# Pebble Developer Documentation SDK 4.33.1. 586 pages. Index: https://developer.repebble.com/llms.txt # Tutorials Source: https://developer.repebble.com/tutorials/ ### Build a Watchface Learn how to create your first watchface. This tutorial will cover basic Pebble concepts, and is the recommended starting point for new developers. [Build with JS (Alloy)](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/)[Build with C](https://developer.repebble.com/tutorials/watchface-tutorial/) ### Learn C with Pebble A community driven, open source textbook that teaches the fundamentals of C through the scope of Pebble application development. [Read the Book](https://pebble.gitbooks.io/learning-c-with-pebble/content/) ### Go Beyond If you're looking to take the next step with your Pebble development, we encourage you to checkout the following resources: - [Advanced Tutorials](https://developer.repebble.com/tutorials/advanced/) - [Create and Publish a Pebble Packages](https://developer.repebble.com/guides/pebble-packages/creating-packages.md) - [Integrate Pebble Packages into your project](https://developer.repebble.com/guides/pebble-packages/using-packages.md) - [Integrate with Pebble Health](https://developer.repebble.com/guides/events-and-services/health.md) - [Use Pebble Dictation Service](https://developer.repebble.com/guides/events-and-services/dictation.md) - [Create Configuration Pages](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) - [Build an Android Companion Apps](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) - [Build an iOS Companion Apps](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) --- # Your First Watchface Source: https://developer.repebble.com/tutorials/watchface-tutorial/part1/ # Your First Watchface This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) > The complete source code for this tutorial is [available on GitHub](https://github.com/coredevices/c-watchface-tutorial). In this tutorial series we will build a fully-featured digital watchface from scratch using Pebble's C API. By the end you will have a watchface with weather support, user configuration, and more. But first, we need to start with the basics. When we are done with this first part, you should have a working watchface that looks something like this: #### aplite #### basalt #### chalk #### diorite #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part1~aplite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part1~basalt.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part1~chalk.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part1~diorite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part1~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part1~gabbro.png) > The screenshots above show the watchface on each Pebble platform. **Aplite** is the original Pebble and Pebble Steel, **Basalt** is the Pebble Time, **Chalk** is the Pebble Time Round, **Diorite** is the Pebble 2, **Emery** is the Pebble Time 2, **Flint** is Pebble 2 Duo, and **Gabbro** is the Pebble Round 2. > > Each platform has different screen dimensions and color capabilities, so it is good practice to test your watchface on all of them. > > **Note** : Flint is not pictured because it is basically identical to Diorite, except for some extra features that we won't be using in this tutorial. ## Creating a New Project Go to [CloudPebble](https://cloudpebble.repebble.com/) and click **Create** to start a new project. Choose **C** as the project type, name it "watchface", and select the **Empty project** template. Then go to **Settings** on the left and set **App Kind** to **Watchface**. Before we begin, make sure you have the Pebble SDK installed. If you haven't done this yet, head over to the [download page](https://developer.repebble.com/sdk.md) to get set up. Once you are ready, navigate to a directory of your choosing and create a new project: ``` $ pebble new-project --simple watchface ``` The `--simple` flag gives us a clean starting point without any sample code. Open `package.json` in the project root and set the `watchapp` object to indicate this is a watchface: ``` "watchapp": {"watchface": true} ``` The key difference between a watchface and a watchapp is that watchfaces serve as the default display on the watch. The Up and Down buttons are reserved for the Pebble timeline, so they are not available for custom behavior. Set an `author` of your choosing, and we are ready to write some code! ## The App Structure Every Pebble C app follows the same basic structure. Click **Add New** next to **Source Files** in the left sidebar to create a new C file, or open the existing `main.c`. Start with the required include and three core functions: Every Pebble C app follows the same basic structure. Open `src/c/main.c` and start with the required include and three core functions: ``` #include \static void init() {}static void deinit() {}int main(void) {init();app\_event\_loop();deinit();} ``` `main()` is the entry point. It calls `init()` to set things up, `app_event_loop()` to let the watchface wait for system events, and `deinit()` to clean up when the app exits. ## Creating a Window Every Pebble app needs at least one [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). Declare a static pointer at the top of the file, below `#include`: ``` static Window \*s\_main\_window; ``` > We prefix static variables with `s_` as a naming convention to indicate they are file-scoped. We also need two handler functions that the system will call when the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is being loaded and unloaded. Create these above `init()`: ``` static void main\_window\_load(Window \*window) {}static void main\_window\_unload(Window \*window) {} ``` Now fill in `init()` to create the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"), assign the handlers, set a background color, and push it onto the window stack: ``` static void init() {// Create main Window element and assign to pointers\_main\_window = window\_create();// Set the background colorwindow\_set\_background\_color(s\_main\_window, GColorBlack);// Set handlers to manage the elements inside the Windowwindow\_set\_window\_handlers(s\_main\_window, (WindowHandlers) {.load = main\_window\_load,.unload = main\_window\_unload});// Show the Window on the watch, with animated=truewindow\_stack\_push(s\_main\_window, true);} ``` Every `_create()` call must be matched with a `_destroy()` call to free memory. Add this to `deinit()`: ``` static void deinit() {// Destroy Windowwindow\_destroy(s\_main\_window);} ``` If you click the **play** button on the right side of the toolbar now, your project will compile and install on the emulator. You will see a black screen. Not very exciting yet, but it confirms everything is working! If you run `pebble build && pebble install` now, you will see a black screen. Not very exciting yet, but it confirms everything is working! ## Displaying the Time To show the time, we will use a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). Declare it below the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") pointer: ``` static TextLayer \*s\_time\_layer; ``` Create the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") inside `main_window_load()`. We use [`PBL_IF_ROUND_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_ROUND_ELSE "PBL\_IF\_ROUND\_ELSE") to position the layer differently on round vs rectangular displays: ``` static void main\_window\_load(Window \*window) {// Get information about the WindowLayer \*window\_layer = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(window\_layer);// Create the time TextLayers\_time\_layer = text\_layer\_create(GRect(0, PBL\_IF\_ROUND\_ELSE(58, 52), bounds.size.w, 50));text\_layer\_set\_background\_color(s\_time\_layer, GColorClear);text\_layer\_set\_text\_color(s\_time\_layer, GColorWhite);text\_layer\_set\_font(s\_time\_layer, fonts\_get\_system\_font(FONT\_KEY\_BITHAM\_42\_BOLD));text\_layer\_set\_text\_alignment(s\_time\_layer, GTextAlignmentCenter);// Add it as a child layer to the Window's root layerlayer\_add\_child(window\_layer, text\_layer\_get\_layer(s\_time\_layer));} ``` And destroy it in `main_window_unload()`: ``` static void main\_window\_unload(Window \*window) {text\_layer\_destroy(s\_time\_layer);} ``` The [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is in place, but it has no text yet. We need the [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") to get the current time. ## Using the TickTimerService The [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") calls a function whenever the time changes. Create a handler above `main_window_load()` matching the required signature: ``` static void tick\_handler(struct tm \*tick\_time, TimeUnits units\_changed) {update\_time();} ``` This calls `update_time()`, which we will create next. The function uses `strftime()` to format the current time into a buffer. The buffer is declared `static` so it persists across calls - the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") only stores a pointer to the text, not a copy: ``` static void update\_time() {// Get a tm structuretime\_t temp = time(NULL);struct tm \*tick\_time = localtime(&temp);// Write the current hours and minutes into a bufferstatic char s\_time\_buffer[8];strftime(s\_time\_buffer, sizeof(s\_time\_buffer), clock\_is\_24h\_style() ?"%H:%M" : "%I:%M", tick\_time);// Display this time on the TextLayertext\_layer\_set\_text(s\_time\_layer, s\_time\_buffer);} ``` `clock_is_24h_style()` respects the user's preference for 12-hour or 24-hour format. Register the tick handler and display the initial time in `init()`, after pushing the window: ``` // Make sure the time is displayed from the startupdate\_time();// Register with TickTimerServicetick\_timer\_service\_subscribe(MINUTE\_UNIT, tick\_handler); ``` `MINUTE_UNIT` means we get a callback every minute. You could use `SECOND_UNIT` for a seconds display, but that costs extra battery. Click the **play** button to compile and install - you should now see the current time! Build and install - you should now see the current time! ## Adding the Date Let's add a second [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") for the date. Declare it at the top: ``` static TextLayer \*s\_date\_layer; ``` Create it in `main_window_load()`, after the time layer: ``` // Create the date TextLayers\_date\_layer = text\_layer\_create(GRect(0, PBL\_IF\_ROUND\_ELSE(110, 104), bounds.size.w, 30));text\_layer\_set\_background\_color(s\_date\_layer, GColorClear);text\_layer\_set\_text\_color(s\_date\_layer, GColorWhite);text\_layer\_set\_font(s\_date\_layer, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_24\_BOLD));text\_layer\_set\_text\_alignment(s\_date\_layer, GTextAlignmentCenter);// Add to Windowlayer\_add\_child(window\_layer, text\_layer\_get\_layer(s\_date\_layer)); ``` Destroy it in `main_window_unload()`: ``` text\_layer\_destroy(s\_date\_layer); ``` Update `update_time()` to also set the date. Add this after the time formatting: ``` // Write the current date into a bufferstatic char s\_date\_buffer[16];strftime(s\_date\_buffer, sizeof(s\_date\_buffer), "%a %b %d", tick\_time);// Display the datetext\_layer\_set\_text(s\_date\_layer, s\_date\_buffer); ``` The format string `"%a %b %d"` produces output like "Mon Jan 01". See the [strftime documentation](http://www.cplusplus.com/reference/ctime/strftime/) for all available format specifiers. ## Conclusion That is the basic process for creating a Pebble watchface! To recap, we: 1. Created a new Pebble project configured as a watchface. 2. Set up the basic app structure with `init()`, `deinit()`, and `main()`. 3. Created a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") with load/unload handlers. 4. Added a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") to display the time. 5. Subscribed to the [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") to keep the time updated. 6. Added a second [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") for the date. If you have problems with your code, check it against [the source code for this part](https://github.com/coredevices/c-watchface-tutorial/tree/main/part1). ## What's Next? The design is functional but plain. In the next part we will spice things up by adding a custom font to give the watchface some personality. [Go to Part 2 →](https://developer.repebble.com/tutorials/watchface-tutorial/part2.md) --- # Customizing Your Watchface Source: https://developer.repebble.com/tutorials/watchface-tutorial/part2/ # Customizing Your Watchface This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) In the previous part we created a basic watchface that displays the time and date using system fonts. It works, but it looks like every other watchface out there. Let's fix that by adding a custom font. By the end of this part, your watchface will look something like this: #### aplite #### basalt #### chalk #### diorite #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part2~aplite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part2~basalt.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part2~chalk.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part2~diorite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part2~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part2~gabbro.png) ## How Resources Work App resources - fonts, images, and other data files - are managed through the **Resources** section in the left sidebar. Click **Add New** next to Resources to upload files and configure their type and identifier. App resources - fonts, images, and other data files - are managed through the `media` array in `package.json`. Each entry specifies the resource type, a name to reference it in code, and the path to the file. All resource files must be placed inside the `resources/` directory of your project. ## Adding a Custom Font A custom font must be a [TrueType](http://en.wikipedia.org/wiki/TrueType) font in the `.ttf` file format. For this tutorial we will use [Jersey 10](https://fonts.google.com/specimen/Jersey+10) from Google Fonts, but you can use any `.ttf` font you like. In CloudPebble, click **Add New** next to **Resources** in the left sidebar. Upload your `.ttf` font file, set the **Resource Type** to **TrueType Font** , and set the **Identifier** to `FONT_JERSEY_56`. Then add the same font file again with the identifier `FONT_JERSEY_24`. We register the same font twice at different sizes - one large size for the time, and a smaller one for the date. Place your font file in `resources/fonts/` and add entries to the `media` array in `package.json`. We will register the same font twice at different sizes - one large size for the time, and a smaller one for the date: ``` "resources": {"media": [{"type": "font","name": "FONT\_JERSEY\_56","file": "fonts/Jersey10-Regular.ttf","compatibility": "2.7"},{"type": "font","name": "FONT\_JERSEY\_24","file": "fonts/Jersey10-Regular.ttf","compatibility": "2.7"}]} ``` The `name` field becomes a constant you can reference in C code, prefixed with `RESOURCE_ID_`. The number at the end of the name (56, 24) is just part of the name you chose - it serves as a reminder of the intended font size. ## Loading Custom Fonts in C Back in `main.c`, declare two [`GFont`](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont "GFont") variables at the top of the file to hold our loaded fonts: ``` static GFont s\_time\_font;static GFont s\_date\_font; ``` Load them in `main_window_load()` using [`fonts_load_custom_font()`](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_load_custom_font "fonts\_load\_custom\_font") and [`resource_get_handle()`](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_get_handle "resource\_get\_handle"): ``` // Load custom fontss\_time\_font = fonts\_load\_custom\_font(resource\_get\_handle(RESOURCE\_ID\_FONT\_JERSEY\_56));s\_date\_font = fonts\_load\_custom\_font(resource\_get\_handle(RESOURCE\_ID\_FONT\_JERSEY\_24)); ``` ## Centering the Layout While we're here, let's properly center the time and date on screen. The date starts 56 pixels below the time and its layer is 30 pixels tall, giving us a total block height. We center the block by subtracting half its height from the screen center: ``` // Center the time + date block verticallyint date\_height = 30;int block\_height = 56 + date\_height;int time\_y = (bounds.size.h / 2) - (block\_height / 2) - 10;int date\_y = time\_y + 56; ``` Notice the `- 10` offset at the end of the `time_y` calculation. Custom fonts often include internal padding (ascent space above the tallest glyph) that shifts the rendered text lower than the calculated position. Subtracting a small offset compensates for this and keeps the block visually centered on screen. You may need to adjust this value depending on the font you choose. Use `time_y` and `date_y` in the [`text_layer_create()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_create "text\_layer\_create") calls instead of the old [`PBL_IF_ROUND_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_ROUND_ELSE "PBL\_IF\_ROUND\_ELSE") values: ``` s\_time\_layer = text\_layer\_create(GRect(0, time\_y, bounds.size.w, 60)); ``` Now replace the system font calls with our custom fonts. Change the [`text_layer_set_font()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_font "text\_layer\_set\_font") calls for both layers: ``` // For the time layer (was FONT\_KEY\_BITHAM\_42\_BOLD)text\_layer\_set\_font(s\_time\_layer, s\_time\_font);// For the date layer (was FONT\_KEY\_GOTHIC\_24\_BOLD)text\_layer\_set\_font(s\_date\_layer, s\_date\_font); ``` ## Cleaning Up Custom fonts must be unloaded when no longer needed. Add the cleanup calls to `main_window_unload()`, after destroying the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer")s: ``` // Unload custom fontsfonts\_unload\_custom\_font(s\_time\_font);fonts\_unload\_custom\_font(s\_date\_font); ``` > Always destroy layers before unloading the fonts they use. The layer may try to access the font during destruction. Click the **play** button to compile and install. You should see your watchface now uses the custom font, giving it a much more distinctive look. Compile and install with `pebble build && pebble install`. You should see your watchface now uses the custom font, giving it a much more distinctive look. ## Experimenting Here are some things you can try: - Use a different font file. There are many free `.ttf` fonts available online at sites like [dafont.com](http://www.dafont.com) and [Google Fonts](https://fonts.google.com). - Adjust the font sizes by changing the `name` values in `package.json`. - Try different Y-positions in [`text_layer_create()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_create "text\_layer\_create") to adjust the layout. > **Tip** : Not all fonts render well at small sizes on the Pebble display. Pixel-style and bitmap fonts tend to look the sharpest. ## Conclusion In this part we learned how to: 1. Register font resources in `package.json`. 2. Load custom fonts with [`fonts_load_custom_font()`](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_load_custom_font "fonts\_load\_custom\_font") and [`resource_get_handle()`](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_get_handle "resource\_get\_handle"). 3. Apply fonts to [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer")s. 4. Clean up font resources properly. Your watchface now has a unique visual identity. Check your code against [the source for this part](https://github.com/coredevices/c-watchface-tutorial/tree/main/part2) if you run into any issues. ## What's Next? In the next part we will add a battery meter and Bluetooth disconnect alerts to give users useful information at a glance. [Go to Part 3 →](https://developer.repebble.com/tutorials/watchface-tutorial/part3.md) --- # Battery Meter and Bluetooth Alerts Source: https://developer.repebble.com/tutorials/watchface-tutorial/part3/ # Battery Meter and Bluetooth Alerts This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) Our watchface tells the time with style, but a great watchface also gives useful information at a glance. In this part we will add two popular features: a battery meter and a Bluetooth disconnect alert. By the end of this part, your watchface will look something like this: #### aplite #### basalt #### chalk #### diorite #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part3~aplite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part3~basalt.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part3~chalk.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part3~diorite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part3~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part3~gabbro.png) This section continues from [_Part 2_](https://developer.repebble.com/tutorials/watchface-tutorial/part2.md), so be sure to re-use your code or start with that finished project. ## The Battery Meter ### Subscribing to Battery Events The battery level is obtained using the [`BatteryStateService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md "BatteryStateService"). Like the [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService"), it works by calling a function whenever the battery state changes. Start by declaring a variable to store the current charge level at the top of your file: ``` static Layer \*s\_battery\_layer;static int s\_battery\_level; ``` Create a callback that stores the new level and triggers a redraw: ``` static void battery\_callback(BatteryChargeState state) {// Record the new battery levels\_battery\_level = state.charge\_percent;// Update the meterlayer\_mark\_dirty(s\_battery\_layer);} ``` [`layer_mark_dirty()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty "layer\_mark\_dirty") tells the system to redraw the layer at the next opportunity. Subscribe to battery events in `init()`: ``` // Register for battery level updatesbattery\_state\_service\_subscribe(battery\_callback);// Ensure battery level is displayed from the startbattery\_callback(battery\_state\_service\_peek()); ``` [`battery_state_service_peek()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#battery_state_service_peek "battery\_state\_service\_peek") returns the current state immediately, so we have a value to display right away. ### Drawing the Battery Bar For the meter we will use a plain [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") with a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") - a callback that handles all the drawing for that layer. This gives us full control over how the bar looks. Our battery bar will have a white rounded-rectangle border and a filled bar inside that changes color based on the charge level - green when healthy, yellow when getting low, red when critical: ``` static void battery\_update\_proc(Layer \*layer, GContext \*ctx) {GRect bounds = layer\_get\_bounds(layer);// Find the width of the bar (inside the border)int bar\_width = ((s\_battery\_level \* (bounds.size.w - 4)) / 100);// Draw the bordergraphics\_context\_set\_stroke\_color(ctx, GColorWhite);graphics\_draw\_round\_rect(ctx, bounds, 2);// Choose color based on battery levelGColor bar\_color;if (s\_battery\_level \<= 20) {bar\_color = PBL\_IF\_COLOR\_ELSE(GColorRed, GColorWhite);} else if (s\_battery\_level \<= 40) {bar\_color = PBL\_IF\_COLOR\_ELSE(GColorChromeYellow, GColorWhite);} else {bar\_color = PBL\_IF\_COLOR\_ELSE(GColorGreen, GColorWhite);}// Draw the filled bar inside the bordergraphics\_context\_set\_fill\_color(ctx, bar\_color);graphics\_fill\_rect(ctx, GRect(2, 2, bar\_width, bounds.size.h - 4), 1, GCornerNone);} ``` > **Note** : [`PBL_IF_COLOR_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE "PBL\_IF\_COLOR\_ELSE") lets us use colors on color-capable platforms (Basalt, Chalk, Emery) while falling back to white on the monochrome Aplite and Diorite. Create the layer in `main_window_load()` and assign the update proc. We center it horizontally and place it near the top of the screen: ``` // Create battery meter Layer - visible bar near the topint bar\_width = bounds.size.w / 2;int bar\_x = (bounds.size.w - bar\_width) / 2;int bar\_y = PBL\_IF\_ROUND\_ELSE(bounds.size.h / 8, bounds.size.h / 28);s\_battery\_layer = layer\_create(GRect(bar\_x, bar\_y, bar\_width, 8));layer\_set\_update\_proc(s\_battery\_layer, battery\_update\_proc);// Add to Windowlayer\_add\_child(window\_layer, s\_battery\_layer); ``` Clean up in `main_window_unload()`: ``` layer\_destroy(s\_battery\_layer); ``` ## Bluetooth Disconnect Alert ### Subscribing to Connection Events The [`ConnectionService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md "ConnectionService") notifies us when the Bluetooth connection changes. We will show an icon when disconnected and vibrate to alert the user. Create a callback: ``` static void bluetooth\_callback(bool connected) {// Show icon if disconnectedlayer\_set\_hidden(bitmap\_layer\_get\_layer(s\_bt\_icon\_layer), connected);if (!connected) {// Issue a vibrating alertvibes\_double\_pulse();}} ``` When `connected` is `true`, the icon is hidden. When `false`, it is shown and the watch vibrates with a double pulse. Subscribe in `init()`: ``` // Register for Bluetooth connection updatesconnection\_service\_subscribe((ConnectionHandlers) {.pebble\_app\_connection\_handler = bluetooth\_callback}); ``` ### Adding the Disconnect Icon We need a small bitmap image to show when disconnected. Here is the icon we will use: ![](https://developer.repebble.com/assets/images/tutorials/intermediate/bt-icon.png) In CloudPebble, click **Add New** next to **Resources** , upload the image, set the **Resource Type** to **Bitmap** , and set the **Identifier** to `IMAGE_BT_ICON`. Save this image to `resources/images/bt-icon.png` in your project. Add it to the `media` array in `package.json`: ``` {"type": "bitmap","name": "IMAGE\_BT\_ICON","file": "images/bt-icon.png"} ``` Declare the [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") and [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") at the top of your file: ``` static BitmapLayer \*s\_bt\_icon\_layer;static GBitmap \*s\_bt\_icon\_bitmap; ``` Create both in `main_window_load()`: ``` // Create the Bluetooth icon GBitmaps\_bt\_icon\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_IMAGE\_BT\_ICON);// Create the BitmapLayer to display the GBitmap - below the battery bar, centeredint bt\_y = bar\_y + 12;s\_bt\_icon\_layer = bitmap\_layer\_create(GRect((bounds.size.w - 30) / 2, bt\_y, 30, 30));bitmap\_layer\_set\_bitmap(s\_bt\_icon\_layer, s\_bt\_icon\_bitmap);bitmap\_layer\_set\_compositing\_mode(s\_bt\_icon\_layer, GCompOpSet);// Add to Windowlayer\_add\_child(window\_get\_root\_layer(window), bitmap\_layer\_get\_layer(s\_bt\_icon\_layer)); ``` Show the correct initial state at the end of `main_window_load()`: ``` // Show the correct state of the BT connection from the startbluetooth\_callback(connection\_service\_peek\_pebble\_app\_connection()); ``` Clean up in `main_window_unload()`: ``` gbitmap\_destroy(s\_bt\_icon\_bitmap);bitmap\_layer\_destroy(s\_bt\_icon\_layer); ``` ## Testing in the Emulator Click the **play** button to compile and install your watchface in the CloudPebble emulator. Build and install your watchface as usual: ``` pebble build && pebble install --emulator emery ``` Once the watchface is running, you can test the battery and Bluetooth features without needing a real watch: ### Setting the Battery Level In the CloudPebble emulator, use the gear menu to adjust the battery level. Try a few different values to see the bar color change. Use `pebble emu-battery` to change the simulated battery level. Try a few different values to see the bar color change: ``` pebble emu-battery --percent 80 pebble emu-battery --percent 30 pebble emu-battery --percent 10 ``` You should see the bar go from green to yellow to red as the level decreases. ### Toggling Bluetooth In the CloudPebble emulator, use the gear menu to toggle the Bluetooth connection on and off. Use `pebble emu-bt-connection` to simulate a Bluetooth disconnect and reconnect: ``` pebble emu-bt-connection --connected no pebble emu-bt-connection --connected yes ``` When you disconnect, the Bluetooth icon should appear and the watch should vibrate. When you reconnect, the icon should disappear. > **Note** : The emulator might take a few seconds before it realizes the connection has been lost. This is normal. Just wait a moment and the icon will appear. ## Conclusion In this part we learned how to: 1. Subscribe to the [`BatteryStateService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md "BatteryStateService") for charge level updates. 2. Draw custom graphics using a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") with a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"). 3. Use color-coded battery levels with [`PBL_IF_COLOR_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE "PBL\_IF\_COLOR\_ELSE"). 4. Subscribe to the [`ConnectionService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md "ConnectionService") for Bluetooth events. 5. Show/hide layers and trigger vibration alerts. 6. Test battery and Bluetooth features using the emulator. Your watchface now shows the battery level and alerts you when the phone disconnects. Check your code against [the source for this part](https://github.com/coredevices/c-watchface-tutorial/tree/main/part3). ## What's Next? In the next part we will add weather information by fetching data from the Open-Meteo API - our first foray into phone-watch communication. [Go to Part 4 →](https://developer.repebble.com/tutorials/watchface-tutorial/part4.md) --- # Adding Weather with Open-Meteo Source: https://developer.repebble.com/tutorials/watchface-tutorial/part4/ # Adding Weather with Open-Meteo This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) Up until now, everything in our watchface has been running entirely on the watch. In this part we take a big step: communicating with the phone to fetch live weather data from the web. We will use [PebbleKit JS](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) to run JavaScript on the connected phone, fetch weather data from the free [Open-Meteo](https://open-meteo.com) API (no API key needed!), and send it to the watch using [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"). This section continues from [_Part 3_](https://developer.repebble.com/tutorials/watchface-tutorial/part3.md). By the end, our watchface will show the current temperature and weather conditions: #### aplite #### basalt #### chalk #### diorite #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part4~aplite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part4~basalt.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part4~chalk.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part4~diorite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part4~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part4~gabbro.png) ## How PebbleKit JS Works Pebble watches cannot access the internet directly. Instead, they communicate with a JavaScript environment called **PebbleKit JS** (PKJS) that runs on the connected phone. Your JS code can make HTTP requests, access GPS, and send data back to the watch using [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"). ``` ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ │ Watch │ │ Phone (PKJS) │ │ Internet │ │ │ │ │ │ │ │ C code ──┼── msg ──>│ index.js ──┼── HTTP ─>│ API server │ │ │ │ │ │ │ │ <── msg ───┼──────────┤ <── response ───┼──────────┤ │ │ │ │ │ │ │ │ AppMessage │ │ geolocation │ │ │ │ request ──┼── msg ──>│ → GPS lookup │ │ │ │ <── msg ───┼──────────┤ → sends coords │ │ │ └─────────────┘ └──────────────────┘ └──────────────┘ ``` The flow for weather is: 1. The watch sends an [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") to the phone requesting weather. 2. PKJS uses `navigator.geolocation` to get GPS coordinates. 3. PKJS makes an HTTP request to a weather API. 4. PKJS sends the result back to the watch via [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"). 5. The watch receives the message and updates the display. All communication between watch and phone uses [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") - a key-value dictionary system. You define the keys in `package.json` and they become constants in both C and JS. ## Preparing the Layout We need a new [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") for the weather data. Add the declaration at the top of the file: ``` static TextLayer \*s\_weather\_layer; ``` Create it in `main_window_load()`. We place it at the bottom of the screen so it stays out of the way of the centered time and date block: ``` // Create weather TextLayer - aligned to the bottom of the screenint weather\_y = bounds.size.h - PBL\_IF\_ROUND\_ELSE(40, 30);s\_weather\_layer = text\_layer\_create(GRect(0, weather\_y, bounds.size.w, 25));text\_layer\_set\_background\_color(s\_weather\_layer, GColorClear);text\_layer\_set\_text\_color(s\_weather\_layer, GColorWhite);text\_layer\_set\_font(s\_weather\_layer, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_18));text\_layer\_set\_text\_alignment(s\_weather\_layer, GTextAlignmentCenter);text\_layer\_set\_text(s\_weather\_layer, "Loading..."); ``` Add it as a child layer and destroy it in `main_window_unload()`: ``` // In main\_window\_load()layer\_add\_child(window\_layer, text\_layer\_get\_layer(s\_weather\_layer));// In main\_window\_unload()text\_layer\_destroy(s\_weather\_layer); ``` ## Setting Up AppMessage [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") is the communication channel between the watch and phone. Messages are key-value dictionaries. In CloudPebble, go to **Settings** on the left sidebar. Check **Uses Location** to allow the phone to access GPS. Then scroll down to **PebbleKit JS Message Keys** and add the following keys: `TEMPERATURE`, `CONDITIONS`, and `REQUEST_WEATHER`. First, define the message keys in `package.json`. Add these to the `pebble` section: ``` "capabilities": ["location"],"messageKeys": ["TEMPERATURE","CONDITIONS","REQUEST\_WEATHER"] ``` The `location` capability allows the phone to access GPS. The message keys become `MESSAGE_KEY_TEMPERATURE`, `MESSAGE_KEY_CONDITIONS`, and `MESSAGE_KEY_REQUEST_WEATHER` constants in C. Now create the AppMessage callbacks in your C file. These go above `init()`: ``` static void inbox\_received\_callback(DictionaryIterator \*iterator, void \*context) {}static void inbox\_dropped\_callback(AppMessageResult reason, void \*context) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Message dropped!");}static void outbox\_failed\_callback(DictionaryIterator \*iterator, AppMessageResult reason, void \*context) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Outbox send failed!");}static void outbox\_sent\_callback(DictionaryIterator \*iterator, void \*context) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Outbox send success!");} ``` Register the callbacks and open AppMessage in `init()`. It is important to register callbacks _before_ opening AppMessage so no messages are missed: ``` // Register AppMessage callbacksapp\_message\_register\_inbox\_received(inbox\_received\_callback);app\_message\_register\_inbox\_dropped(inbox\_dropped\_callback);app\_message\_register\_outbox\_failed(outbox\_failed\_callback);app\_message\_register\_outbox\_sent(outbox\_sent\_callback);// Open AppMessageconst int inbox\_size = 128;const int outbox\_size = 128;app\_message\_open(inbox\_size, outbox\_size); ``` ## Writing the JavaScript Click **Add New** next to **Source Files** in the left sidebar, select **JavaScript file** , and name it (e.g., `weather.js`). This code runs on the phone whenever the watchface is open. Create a new file at `src/pkjs/index.js`. This code runs on the phone whenever the watchface is open. Start with a helper function for making HTTP requests: ``` var xhrRequest = function (url, type, callback) {var xhr = new XMLHttpRequest();xhr.onload = function () {callback(this.responseText);};xhr.open(type, url);xhr.send();}; ``` Next, a function to convert Open-Meteo weather codes to readable strings: ``` function weatherCodeToCondition(code) {if (code === 0) return 'Clear';if (code \<= 3) return 'Cloudy';if (code \<= 48) return 'Fog';if (code \<= 55) return 'Drizzle';if (code \<= 57) return 'Fz. Drizzle';if (code \<= 65) return 'Rain';if (code \<= 67) return 'Fz. Rain';if (code \<= 75) return 'Snow';if (code \<= 77) return 'Snow Grains';if (code \<= 82) return 'Showers';if (code \<= 86) return 'Snow Shwrs';if (code === 95) return 'T-Storm';if (code \<= 99) return 'T-Storm';return 'Unknown';} ``` Now the weather fetching logic. We first get the user's location, then call the Open-Meteo API with those coordinates: ``` function locationSuccess(pos) {var url = 'https://api.open-meteo.com/v1/forecast?' +'latitude=' + pos.coords.latitude +'&longitude=' + pos.coords.longitude +'¤t=temperature\_2m,weather\_code';xhrRequest(url, 'GET',function(responseText) {var json = JSON.parse(responseText);var temperature = Math.round(json.current.temperature\_2m);var conditions = weatherCodeToCondition(json.current.weather\_code);var dictionary = {'TEMPERATURE': temperature,'CONDITIONS': conditions};Pebble.sendAppMessage(dictionary,function(e) { console.log('Weather info sent!'); },function(e) { console.log('Error sending weather info!'); });});}function locationError(err) {console.log('Error requesting location!');}function getWeather() {navigator.geolocation.getCurrentPosition(locationSuccess,locationError,{ timeout: 15000, maximumAge: 60000 });} ``` > **Why Open-Meteo?** Unlike many weather APIs, Open-Meteo is completely free and requires no API key. The URL is simple and the response is clean JSON. Finally, set up the event listeners: ``` Pebble.addEventListener('ready',function(e) {console.log('PebbleKit JS ready!');getWeather();});Pebble.addEventListener('appmessage',function(e) {console.log('AppMessage received!');if (e.payload['REQUEST\_WEATHER']) {getWeather();}}); ``` When the JS environment starts (`ready`), we fetch weather immediately. The `appmessage` listener checks for the `REQUEST_WEATHER` key before refreshing, so it only fetches weather when the watch explicitly asks for it. ## Processing Weather Data on the Watch Back in the C file, fill in `inbox_received_callback()` to extract the weather data and display it: ``` static void inbox\_received\_callback(DictionaryIterator \*iterator, void \*context) {Tuple \*temp\_tuple = dict\_find(iterator, MESSAGE\_KEY\_TEMPERATURE);Tuple \*conditions\_tuple = dict\_find(iterator, MESSAGE\_KEY\_CONDITIONS);if (temp\_tuple && conditions\_tuple) {static char temperature\_buffer[8];static char conditions\_buffer[32];static char weather\_layer\_buffer[42];snprintf(temperature\_buffer, sizeof(temperature\_buffer), "%d°C", (int)temp\_tuple-\>value-\>int32);snprintf(conditions\_buffer, sizeof(conditions\_buffer), "%s", conditions\_tuple-\>value-\>cstring);snprintf(weather\_layer\_buffer, sizeof(weather\_layer\_buffer), "%s %s", temperature\_buffer, conditions\_buffer);text\_layer\_set\_text(s\_weather\_layer, weather\_layer\_buffer);}} ``` We use [`dict_find()`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_find "dict\_find") to look up each key. Numbers arrive as `int32`, strings as `cstring`. ## Automatic Refresh To keep the weather current, trigger a refresh every 30 minutes from the tick handler: ``` static void tick\_handler(struct tm \*tick\_time, TimeUnits units\_changed) {update\_time();// Get weather update every 30 minutesif (tick\_time-\>tm\_min % 30 == 0) {DictionaryIterator \*iter;app\_message\_outbox\_begin(&iter);dict\_write\_uint8(iter, MESSAGE\_KEY\_REQUEST\_WEATHER, 1);app\_message\_outbox\_send();}} ``` This sends an AppMessage with the `REQUEST_WEATHER` key to the phone. The JS `appmessage` listener sees this key and calls `getWeather()`. ## Conclusion In this part we learned how to: 1. Set up [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") for watch-phone communication. 2. Write PebbleKit JS to run on the phone. 3. Use `navigator.geolocation` to get the user's location. 4. Fetch data from a web API using `XMLHttpRequest`. 5. Parse a JSON response and send data to the watch. 6. Display received data in a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). 7. Set up automatic refresh via the tick handler. Your watchface now shows live weather data! Check your code against [the source for this part](https://github.com/coredevices/c-watchface-tutorial/tree/main/part4). ## What's Next? In the next part we will add Timeline Peek support, so the watchface adapts gracefully when the system overlays part of the screen. [Go to Part 5 →](https://developer.repebble.com/tutorials/watchface-tutorial/part5.md) --- # Timeline Peek Source: https://developer.repebble.com/tutorials/watchface-tutorial/part5/ # Timeline Peek This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) Pebble's Timeline Quick View can appear at the bottom of the screen to show upcoming events. When it does, it covers part of your watchface. In this part we will use the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API to gracefully adapt our layout - repositioning the time, date, and weather to fit in the remaining space. Here is what the transition looks like: #### basalt #### diorite #### emery ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part5~basalt.gif) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part5~diorite.gif) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part5~emery.gif) This section continues from [_Part 4_](https://developer.repebble.com/tutorials/watchface-tutorial/part4.md), so be sure to re-use your code or start with that finished project. ## How Timeline Quick View Works Timeline Quick View is a system overlay that obstructs the bottom ~51 pixels of the screen (including a 2px border). Your watchface is not required to handle this, but with the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API you can detect the change and rearrange your layers for a better experience. > **Note** : Timeline Quick View is not currently supported for the Round platforms (chalk and gabbro), but writing compatible code ensures your watchface handles it gracefully on all platforms. ## Getting Unobstructed Bounds Prior to SDK 4.0, you would use [`layer_get_bounds()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds "layer\_get\_bounds") to get the full screen size. The [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API adds [`layer_get_unobstructed_bounds()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds "layer\_get\_unobstructed\_bounds"), which returns only the area not covered by a system overlay: ``` GRect full\_bounds = layer\_get\_bounds(s\_window\_layer);GRect unobstructed\_bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer); ``` If there is no overlay, both return the same rectangle. ## Keeping a Reference to the Window Layer We need access to the root layer in our handler function, so store it in a file-level variable: ``` static Layer \*s\_window\_layer; ``` Set it at the start of `main_window_load()`: ``` s\_window\_layer = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(s\_window\_layer); ``` ## Subscribing to Unobstructed Area Events The API provides three event handlers: - `.will_change` - fires before the obstruction appears or disappears - `.change` - fires repeatedly during the animation - `.did_change` - fires after the animation completes We will use all three to demonstrate the full lifecycle. Subscribe at the end of `main_window_load()`: ``` UnobstructedAreaHandlers handlers = {.will\_change = prv\_unobstructed\_will\_change,.change = prv\_unobstructed\_change,.did\_change = prv\_unobstructed\_did\_change};unobstructed\_area\_service\_subscribe(handlers, NULL); ``` > **Important** : Construct the [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers") struct before passing it to [`unobstructed_area_service_subscribe()`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_subscribe "unobstructed\_area\_service\_subscribe"). ## Implementing the Handlers We use three handlers to split responsibilities cleanly: ### Before the animation - `.will_change` This fires once before the overlay starts moving. We hide the Bluetooth disconnect icon during the transition so it does not overlap with repositioning layers: ``` static void prv\_unobstructed\_will\_change(GRect final\_unobstructed\_screen\_area,void \*context) {// Hide BT icon during the transition to reduce clutterlayer\_set\_hidden(bitmap\_layer\_get\_layer(s\_bt\_icon\_layer), true);} ``` Note the different signature - `.will_change` receives the _final_ unobstructed area as a [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect"), so you could use it to prepare for the target layout. ### During the animation - `.change` This fires repeatedly as the overlay slides in or out. We recalculate all positions from the current unobstructed bounds so the layers animate smoothly: ``` static void prv\_unobstructed\_change(AnimationProgress progress, void \*context) {GRect bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer);// Reposition time, date, and weather to fit in the available spaceint date\_height = 30;int block\_height = 56 + date\_height;int time\_y = (bounds.size.h / 2) - (block\_height / 2) - 10;int date\_y = time\_y + 56;int weather\_y = bounds.size.h - PBL\_IF\_ROUND\_ELSE(40, 30);GRect time\_frame = layer\_get\_frame(text\_layer\_get\_layer(s\_time\_layer));time\_frame.origin.y = time\_y;layer\_set\_frame(text\_layer\_get\_layer(s\_time\_layer), time\_frame);GRect date\_frame = layer\_get\_frame(text\_layer\_get\_layer(s\_date\_layer));date\_frame.origin.y = date\_y;layer\_set\_frame(text\_layer\_get\_layer(s\_date\_layer), date\_frame);GRect weather\_frame = layer\_get\_frame(text\_layer\_get\_layer(s\_weather\_layer));weather\_frame.origin.y = weather\_y;layer\_set\_frame(text\_layer\_get\_layer(s\_weather\_layer), weather\_frame);} ``` Since we use the same `(bounds.size.h / 2) - (block_height / 2) - 10` formula as in `main_window_load()`, the time+date block stays centered in the available space - as the unobstructed bounds shrink, so does the offset, and all layers naturally slide together. ### After the animation - `.did_change` This fires once after the overlay finishes moving. If the screen is still obstructed we keep the Bluetooth icon hidden - there is not enough room for it. If the screen is back to full size we restore the icon based on the actual connection state: ``` static void prv\_unobstructed\_did\_change(void \*context) {GRect full\_bounds = layer\_get\_bounds(s\_window\_layer);GRect bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer);bool obstructed = !grect\_equal(&full\_bounds, &bounds);// Keep BT icon hidden when obstructed, otherwise restore based on connectionif (obstructed) {layer\_set\_hidden(bitmap\_layer\_get\_layer(s\_bt\_icon\_layer), true);} else {layer\_set\_hidden(bitmap\_layer\_get\_layer(s\_bt\_icon\_layer),connection\_service\_peek\_pebble\_app\_connection());}} ``` Notice that each handler has a different signature - this is a common pattern in the Pebble SDK. ### Handling Quick View on startup Timeline Quick View may already be active when the watchface starts. Since the handlers only fire during transitions, we need to apply the correct layout immediately. Call the `.change` and `.did_change` handlers manually before subscribing, replacing the old `bluetooth_callback` call: ``` // Apply correct layout in case Quick View is already activeprv\_unobstructed\_change(0, NULL);prv\_unobstructed\_did\_change(NULL); ``` This repositions the layers and sets the BT icon visibility based on the current obstruction state, so the watchface looks correct from the first frame. ### How it all fits together As the overlay slides in, the unobstructed bounds shrink, and all three text layers slide up together. The Bluetooth icon hides during the transition and stays hidden while the screen is obstructed. When the overlay goes away, the icon reappears if the phone is disconnected. Everything stays on screen - nothing is hidden, just repositioned. ## Testing Timeline Quick View You can toggle Timeline Quick View using the emulator controls in CloudPebble. You can toggle Timeline Quick View in the emulator: ``` $ pebble emu-set-timeline-quick-view on$ pebble emu-set-timeline-quick-view off ``` When enabled, you should see the time, date, and weather squeeze together into the remaining space. The Bluetooth icon hides during the transition and reappears based on connection state once it finishes. When disabled, everything returns to its original position. ## Conclusion In this part we learned how to: 1. Use [`layer_get_unobstructed_bounds()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds "layer\_get\_unobstructed\_bounds") to find available screen space. 2. Subscribe to all three [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers") callbacks. 3. Use `.will_change` to prepare, `.change` to animate, and `.did_change` to finalize. 4. Reposition layers dynamically to fit in the unobstructed area. Your watchface now adapts gracefully to Timeline Quick View. Check your code against [the source for this part](https://github.com/coredevices/c-watchface-tutorial/tree/main/part5). ## What's Next? In the next part we will add a settings page with Clay - letting users pick colors, toggle the date, and choose temperature units. [Go to Part 6 →](https://developer.repebble.com/tutorials/watchface-tutorial/part6.md) --- # Adding a settings page Source: https://developer.repebble.com/tutorials/watchface-tutorial/part6/ # Adding a settings page This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) The finishing touch for any great watchface is letting users make it their own. In this final part we will add a configuration page using [Clay for Pebble](https://github.com/pebble-dev/clay), which generates a settings UI on the phone from a simple JSON definition. Users will be able to pick colors, choose temperature units, and toggle the date display. Here is an example of a customized watchface: #### aplite #### basalt #### chalk #### diorite #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6~aplite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6~basalt.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6~chalk.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6~diorite.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6~gabbro.png) This section continues from [_Part 5_](https://developer.repebble.com/tutorials/watchface-tutorial/part5.md). ## Installing Clay In CloudPebble, go to the **Dependencies** section in the left sidebar and add `@rebble/clay` as a NPM Dependency. Clay is available as a Pebble Package. Install it from your project directory: ``` $ pebble package install @rebble/clay ``` This adds `@rebble/clay` to the `dependencies` in `package.json`. ## Enabling Configuration In CloudPebble, go to **Settings** and add `configurable` to the **Capabilities** list so the gear icon appears next to your watchface in the phone app. For the gear icon to appear next to your watchface in the phone app, add `configurable` to the `capabilities` array in `package.json`: ``` "capabilities": ["location","configurable"] ``` ## Defining Message Keys In CloudPebble, go to **Settings** and add the following message keys in the **PebbleKit JS Message Keys** section: `BackgroundColor`, `TextColor`, `TemperatureUnit`, and `ShowDate`. We need message keys for each setting. Add these to the `messageKeys` array, alongside the existing weather keys: ``` "messageKeys": ["TEMPERATURE","CONDITIONS","REQUEST\_WEATHER","BackgroundColor","TextColor","TemperatureUnit","ShowDate"] ``` These become `MESSAGE_KEY_BackgroundColor`, `MESSAGE_KEY_TextColor`, etc. in C. ## Creating the Clay Configuration Click **Add New** next to **Source Files** in the left sidebar, select **JavaScript file** , and name it `config.js`. Clay uses a simple JSON array of sections and fields: Create `src/pkjs/config.js` with the configuration definition. Clay uses a simple JSON array of sections and fields: ``` module.exports = [{"type": "heading","defaultValue": "Watchface Settings"},{"type": "text","defaultValue": "Customize your watchface appearance and preferences."},{"type": "section","items": [{"type": "heading","defaultValue": "Colors"},{"type": "color","messageKey": "BackgroundColor","defaultValue": "0x000000","label": "Background Color"},{"type": "color","messageKey": "TextColor","defaultValue": "0xFFFFFF","label": "Text Color"}]},{"type": "section","items": [{"type": "heading","defaultValue": "Preferences"},{"type": "toggle","messageKey": "TemperatureUnit","label": "Use Fahrenheit","defaultValue": false},{"type": "toggle","messageKey": "ShowDate","label": "Show Date","defaultValue": true}]},{"type": "submit","defaultValue": "Save Settings"}]; ``` Each `messageKey` matches a key in `package.json`. The `color` type provides a color picker, `toggle` gives a switch. ## Initializing Clay in JavaScript Add three lines at the top of `src/pkjs/index.js`, before any other code: ``` var Clay = require('@rebble/clay');var clayConfig = require('./config');var clay = new Clay(clayConfig); ``` Clay automatically handles the `showConfiguration` and `webviewClosed` events. Your existing weather code works alongside Clay without changes. ## Persisting Settings on the Watch On the C side, define a struct to hold all settings and use persistent storage to save them across app restarts. At the top of the file: ``` #define SETTINGS\_KEY 1typedef struct ClaySettings {GColor BackgroundColor;GColor TextColor;bool TemperatureUnit; // false = Celsius, true = Fahrenheitbool ShowDate;} ClaySettings;static ClaySettings settings; ``` Add helper functions for defaults, save, and load: ``` static void prv\_default\_settings() {settings.BackgroundColor = GColorBlack;settings.TextColor = GColorWhite;settings.TemperatureUnit = false;settings.ShowDate = true;}static void prv\_save\_settings() {persist\_write\_data(SETTINGS\_KEY, &settings, sizeof(settings));}static void prv\_load\_settings() {prv\_default\_settings();persist\_read\_data(SETTINGS\_KEY, &settings, sizeof(settings));} ``` `prv_load_settings()` sets defaults first, then overwrites with any saved data. This ensures new fields always have valid defaults. Call `prv_load_settings()` at the start of `init()`, before creating the window: ``` static void init() {prv\_load\_settings();// ... rest of init} ``` ## Applying Settings to the UI Create a function that updates all visual elements based on current settings: ``` static void prv\_update\_display() {window\_set\_background\_color(s\_main\_window, settings.BackgroundColor);text\_layer\_set\_text\_color(s\_time\_layer, settings.TextColor);text\_layer\_set\_text\_color(s\_date\_layer, settings.TextColor);text\_layer\_set\_text\_color(s\_weather\_layer, settings.TextColor);// Show/hide date based on settinglayer\_set\_hidden(text\_layer\_get\_layer(s\_date\_layer), !settings.ShowDate);layer\_mark\_dirty(s\_battery\_layer);} ``` Call this at the end of `main_window_load()` to apply saved settings on startup. We also need to update the battery drawing. The color-coded levels from Part 3 stay the same on color platforms, but we use `settings.TextColor` for the border and as the monochrome fallback so the bar respects the user's color choice: ``` static void battery\_update\_proc(Layer \*layer, GContext \*ctx) {GRect bounds = layer\_get\_bounds(layer);// Find the width of the bar (inside the border)int bar\_width = ((s\_battery\_level \* (bounds.size.w - 4)) / 100);// Draw the border using the text colorgraphics\_context\_set\_stroke\_color(ctx, settings.TextColor);graphics\_draw\_round\_rect(ctx, bounds, 2);// Choose color based on battery levelGColor bar\_color;if (s\_battery\_level \<= 20) {bar\_color = PBL\_IF\_COLOR\_ELSE(GColorRed, settings.TextColor);} else if (s\_battery\_level \<= 40) {bar\_color = PBL\_IF\_COLOR\_ELSE(GColorChromeYellow, settings.TextColor);} else {bar\_color = PBL\_IF\_COLOR\_ELSE(GColorGreen, settings.TextColor);}// Draw the filled bar inside the bordergraphics\_context\_set\_fill\_color(ctx, bar\_color);graphics\_fill\_rect(ctx, GRect(2, 2, bar\_width, bounds.size.h - 4), 1, GCornerNone);} ``` ## Handling Settings in the Inbox The tricky part: our `inbox_received_callback` now handles two types of messages - weather data AND configuration changes. We differentiate by checking which keys are present: ``` static void inbox\_received\_callback(DictionaryIterator \*iterator, void \*context) {// Check for weather dataTuple \*temp\_tuple = dict\_find(iterator, MESSAGE\_KEY\_TEMPERATURE);Tuple \*conditions\_tuple = dict\_find(iterator, MESSAGE\_KEY\_CONDITIONS);if (temp\_tuple && conditions\_tuple) {static char temperature\_buffer[8];static char conditions\_buffer[32];static char weather\_layer\_buffer[42];int temp\_value = (int)temp\_tuple-\>value-\>int32;// Convert to Fahrenheit if setting is enabledif (settings.TemperatureUnit) {temp\_value = (temp\_value \* 9 / 5) + 32;snprintf(temperature\_buffer, sizeof(temperature\_buffer), "%d°F", temp\_value);} else {snprintf(temperature\_buffer, sizeof(temperature\_buffer), "%d°C", temp\_value);}snprintf(conditions\_buffer, sizeof(conditions\_buffer), "%s", conditions\_tuple-\>value-\>cstring);snprintf(weather\_layer\_buffer, sizeof(weather\_layer\_buffer), "%s %s", temperature\_buffer, conditions\_buffer);text\_layer\_set\_text(s\_weather\_layer, weather\_layer\_buffer);}// Check for Clay settingsTuple \*bg\_color\_t = dict\_find(iterator, MESSAGE\_KEY\_BackgroundColor);if (bg\_color\_t) {settings.BackgroundColor = GColorFromHEX(bg\_color\_t-\>value-\>int32);}Tuple \*text\_color\_t = dict\_find(iterator, MESSAGE\_KEY\_TextColor);if (text\_color\_t) {settings.TextColor = GColorFromHEX(text\_color\_t-\>value-\>int32);}Tuple \*temp\_unit\_t = dict\_find(iterator, MESSAGE\_KEY\_TemperatureUnit);if (temp\_unit\_t) {settings.TemperatureUnit = temp\_unit\_t-\>value-\>int32 == 1;}Tuple \*show\_date\_t = dict\_find(iterator, MESSAGE\_KEY\_ShowDate);if (show\_date\_t) {settings.ShowDate = show\_date\_t-\>value-\>int32 == 1;}// Save and apply if any settings were changedif (bg\_color\_t || text\_color\_t || temp\_unit\_t || show\_date\_t) {prv\_save\_settings();prv\_update\_display();// Refetch weather if the temperature unit changed so the display updatesif (temp\_unit\_t) {DictionaryIterator \*iter;app\_message\_outbox\_begin(&iter);dict\_write\_uint8(iter, MESSAGE\_KEY\_REQUEST\_WEATHER, 1);app\_message\_outbox\_send();}}} ``` Weather messages contain `TEMPERATURE` and `CONDITIONS` keys. Clay messages contain `BackgroundColor`, `TextColor`, etc. Both can be handled independently in the same callback. Notice that when the temperature unit changes we immediately request a weather refresh. Without this, the display would keep showing the old unit until the next scheduled 30-minute update. Also increase the AppMessage buffer sizes in `init()` to accommodate the larger Clay messages: ``` const int inbox\_size = 256;const int outbox\_size = 256; ``` ## Trying It Out Click the **play** button to compile and install, then tap the gear icon in the emulator to open the settings page. Build and install your watchface, then use `pebble emu-app-config` to open the settings page in your browser: ``` $ pebble build && pebble install --emulator emery$ pebble emu-app-config ``` Try changing the background color, text color, and toggling the date and temperature unit - you should see the watchface update immediately. ![Settings page on phone](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6-settings.gif) ## Updating Timeline Peek for Settings Since our Timeline Peek handlers reposition layers rather than hiding them, no changes are needed there for Clay settings. The ShowDate toggle is handled entirely in `prv_update_display()` - when the date is hidden, it simply stays hidden regardless of the obstruction state. ## Conclusion Congratulations! You have built a complete, feature-rich Pebble watchface. Here is everything it includes: 1. **Digital time display** with a custom font. 2. **Date display** that can be toggled on/off. 3. **Live weather** from Open-Meteo (no API key needed). 4. **Battery meter** drawn with custom graphics. 5. **Bluetooth disconnect** icon and vibration alert. 6. **Timeline Peek** support with smooth animations. 7. **Clay configuration** for colors and preferences. 8. **Persistent settings** that survive app restarts. In this final part we learned how to: - Install and configure Clay for Pebble. - Define a settings page with color pickers and toggles. - Persist settings on the watch with [`persist_write_data()`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_data "persist\_write\_data"). - Handle mixed message types (weather + config) in a single inbox callback. - Apply settings dynamically to all UI elements. Check your code against [the source for this part](https://github.com/coredevices/c-watchface-tutorial/tree/main/part6). Now it is time to [_publish your watchface_](https://developer.repebble.com/guides/appstore-publishing.md) in the Pebble appstore. --- # Your First Watchface Source: https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part1/ # Your First Watchface This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) > The complete source code for this tutorial is [available on GitHub](https://github.com/coredevices/alloy-watchface-tutorial). In this tutorial series we will build a fully-featured digital watchface from scratch using Alloy, Pebble's modern JavaScript framework. By the end you will have a watchface with weather support, user settings, and more. But first, we need to start with the basics. > **Platform Support** : Alloy currently supports Emery (Pebble Time 2) and Gabbro (Pebble Round 2). When we are done with this first part, you should have a working watchface that displays the time and date on a black background: #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part1~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part1~gabbro.png) ## Creating a New Project Go to [CloudPebble](https://cloudpebble.repebble.com/) and click **Create** to start a new project. Choose **JavaScript SDK (beta)** as the project type (this is Alloy) and name it "watchface". Then go to **Settings** on the left and set **App Kind** to **Watchface**. Before we begin, make sure you have the Pebble SDK installed. If you haven't done this yet, head over to the [download page](https://developer.repebble.com/sdk.md) to get set up. Once you are ready, navigate to a directory of your choosing and create a new Alloy project: ``` $ pebble new-project --alloy watchface ``` The most important file is `main.js` - this is where your watchface code runs on the watch. This creates a new folder with the basic structure for an Alloy app. The most important file is `src/embeddedjs/main.js` - this is where your watchface code runs on the watch. (See the [Getting Started guide](https://developer.repebble.com/guides/alloy/getting-started.md) for more details on project structure.) Open `package.json` and make sure the `watchapp` object indicates this is a watchface: ``` "watchapp": {"watchface": true} ``` The key difference between a watchface and a watchapp is that watchfaces serve as the default display on the watch. The Up and Down buttons are reserved for the Pebble timeline, so they are not available for custom behavior. ## Understanding Alloy Watchfaces Every Alloy watchface follows the same basic pattern: 1. **Import Poco** for graphics rendering 2. **Create a renderer** from the `screen` global 3. **Set up fonts and colors** once at startup 4. **Register a time event** that redraws the display Open `main.js` in the CloudPebble editor and replace its contents with: Open `src/embeddedjs/main.js` and replace its contents with: ``` import Poco from "commodetto/Poco";const render = new Poco(screen); ``` `Poco` is a low-level graphics library for drawing on the screen. The `screen` global provides access to the Pebble display. ## Setting Up Fonts and Colors Before we can draw anything, we need a font and some colors. Add these below the renderer: ``` // Fontsconst timeFont = new render.Font("Bitham-Bold", 42);const dateFont = new render.Font("Gothic-Bold", 24);// Colorsconst black = render.makeColor(0, 0, 0);const white = render.makeColor(255, 255, 255); ``` Alloy comes with several built-in Pebble fonts. `Bitham-Bold` at 42px is a good size for a time display - bold and easy to read at a glance. `makeColor()` takes RGB values (0–255) and returns a color value optimized for the display. ## Displaying the Time Now let's write a draw function. All Poco drawing happens between `begin()` and `end()` calls: ``` function draw(event) {const now = event.date;render.begin();render.fillRectangle(black, 0, 0, render.width, render.height);// Format time as HH:MMconst hours = String(now.getHours()).padStart(2, "0");const minutes = String(now.getMinutes()).padStart(2, "0");const timeStr = `${hours}:${minutes}`;// Center the time on screenlet width = render.getTextWidth(timeStr, timeFont);render.drawText(timeStr, timeFont, white,(render.width - width) / 2,(render.height / 2) - timeFont.height + 5);render.end();} ``` The `event` parameter contains a `date` property with the current `Date` object - no need to call `new Date()` yourself. We use `padStart()` to ensure single-digit hours and minutes are zero-padded (e.g., "09:05" instead of "9:5"). `getTextWidth()` measures how many pixels wide the rendered text will be, which lets us center it horizontally. Now register the draw function for time updates: ``` watch.addEventListener("minutechange", draw); ``` `minutechange` fires once every minute. The callback is also invoked immediately when the event listener is registered, so the watchface draws right away without needing a separate startup call. You could use `secondchange` for a seconds display, but that costs extra battery. Click the **play** button to compile and install - you should now see the current time! Build and install - you should now see the current time! ``` $ pebble build$ pebble install --emulator emery ``` ## Adding the Date Let's add a date display below the time. We need day and month name arrays since JavaScript's `Date` only provides numeric values: ``` const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; ``` Now update the `draw()` function to include the date. Add this after the time drawing, before `render.end()`: ``` // Format date as "Mon Jan 01"const dayName = DAYS[now.getDay()];const monthName = MONTHS[now.getMonth()];const dateStr = `${dayName} ${monthName} ${String(now.getDate()).padStart(2, "0")}`;// Draw date below the timewidth = render.getTextWidth(dateStr, dateFont);render.drawText(dateStr, dateFont, white,(render.width - width) / 2,(render.height / 2) + 10); ``` Build and install. You should now see both the time and date. ## Conclusion That is the basic process for creating a Pebble watchface with Alloy! To recap, we: 1. Created a new Alloy project configured as a watchface. 2. Imported Poco and created a renderer. 3. Set up fonts and colors. 4. Drew the time centered on screen. 5. Registered a `minutechange` event listener to keep the display updated. 6. Added a date display. If you have problems with your code, check it against [the source code for this part](https://github.com/coredevices/alloy-watchface-tutorial/tree/main/part1). ## What's Next? The design is functional but plain. In the next part we will switch to a more distinctive font and properly center the layout. [Go to Part 2 →](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part2.md) --- # Customizing Your Watchface Source: https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part2/ # Customizing Your Watchface This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) In the previous part we created a basic watchface that displays the time and date using `Bitham-Bold`. It works, but it looks like every other watchface out there. Let's fix that by switching to a custom font - the same Jersey font used in the C watchface tutorial - and improving the layout. By the end of this part, your watchface will look something like this: #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part2~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part2~gabbro.png) This section continues from [_Part 1_](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part1.md), so be sure to re-use your code or start with that finished project. ## Adding a Custom Font Alloy supports custom TrueType fonts through the Moddable SDK's font pipeline. The build system converts `.ttf` files into optimized bitmap resources that Poco can render efficiently. ### Getting the Font File Download [Jersey10-Regular.ttf](https://fonts.google.com/specimen/Jersey+10) (it's a free Google Font). In CloudPebble, click **Add New** next to **Resources** in the left sidebar. In the upload dialog, leave the path prefix as `assets/`, select your `Jersey10-Regular.ttf` file, and click **Upload**. Place the font file in your project: ``` src/ embeddedjs/ assets/ Jersey10-Regular.ttf main.js manifest.json ``` ### Declaring Font Resources Having the font file in your project is not enough on its own - we need to tell the Moddable build system how to process it. Font files must be declared in `manifest.json` with a size and character set so the build system can convert them into optimized bitmap resources. Open `manifest.json` in the **Embedded JS** section of the sidebar. Update the `resources` section to declare the font at the two sizes we need: Create `src/embeddedjs/manifest.json` (or update it if it already exists). Add the font to the `resources` section at the two sizes we need: ``` {"include": ["$(MODDABLE)/examples/manifest\_mod.json"],"modules": {"\*": "./main.js"},"resources": {"\*-alpha": [{"source": "./assets/Jersey10-Regular","size": 56,"monochrome": true,"blocks": ["Basic Latin"]},{"source": "./assets/Jersey10-Regular","size": 24,"monochrome": true,"blocks": ["Basic Latin"]}]}} ``` Key properties: - **`source`** - path to the `.ttf` file (without the extension), relative to the manifest - **`size`** - font size in pixels to render at - **`monochrome`** - `true` for crisp 1-bit rendering (ideal for Pebble) - **`blocks`** - which Unicode character blocks to include. `"Basic Latin"` covers the digits, letters, and punctuation we need. Including only the characters you need saves memory. We declare the font at two sizes: 56 for the time display (same as the C tutorial's `FONT_JERSEY_56`) and 24 for the date. ### Loading Custom Fonts in Code In the C SDK, you load a custom font with `fonts_load_custom_font()`. In Alloy, you load the two generated resource files (`.fnt` for metrics, `-alpha.bm4` for pixel data) and combine them: ``` import parseBMF from "commodetto/parseBMF";import parseRLE from "commodetto/parseRLE";function getFont(name, size) {const font = parseBMF(new Resource(`${name}-${size}.fnt`));font.bitmap = parseRLE(new Resource(`${name}-${size}-alpha.bm4`));return font;} ``` Now replace the built-in font declarations with custom font loading: ``` // Was: const timeFont = new render.Font("Bitham-Bold", 42);// Was: const dateFont = new render.Font("Gothic-Bold", 24);const timeFont = getFont("Jersey10-Regular", 56);const dateFont = getFont("Jersey10-Regular", 24); ``` The returned font object works exactly like a built-in font - you can pass it to `render.drawText()`, `render.getTextWidth()`, and read its `.height` property. ## Centering the Layout In Part 1 we positioned the time and date using hardcoded offsets. Let's properly center the time+date block vertically by calculating positions based on the font metrics. Add these calculations at the top level, after the font declarations: ``` // Precompute layout positionsconst blockHeight = timeFont.height + dateFont.height;const timeY = (render.height - blockHeight) / 2;const dateY = timeY + timeFont.height; ``` The `height` property on a font gives the line height in pixels. We center the combined block on screen by computing half the total height. Since these values only depend on the font sizes and screen dimensions, we compute them once at startup rather than every frame. This is a good habit for embedded development - precompute what you can. Now update the `draw()` function to use `timeY` and `dateY`: ``` function draw(event) {const now = event.date;render.begin();render.fillRectangle(black, 0, 0, render.width, render.height);// Format time as HH:MMconst hours = String(now.getHours()).padStart(2, "0");const minutes = String(now.getMinutes()).padStart(2, "0");const timeStr = `${hours}:${minutes}`;// Draw time centeredlet width = render.getTextWidth(timeStr, timeFont);render.drawText(timeStr, timeFont, white,(render.width - width) / 2, timeY);// Format date as "Mon Jan 01"const dayName = DAYS[now.getDay()];const monthName = MONTHS[now.getMonth()];const dateStr = `${dayName} ${monthName} ${String(now.getDate()).padStart(2, "0")}`;// Draw date centered below timewidth = render.getTextWidth(dateStr, dateFont);render.drawText(dateStr, dateFont, white,(render.width - width) / 2, dateY);render.end();} ``` Click the **play** button to compile and install. You should see the watchface now uses Jersey - the same distinctive font from the C tutorial - with the time and date properly centered as a block. Compile and install with `pebble build && pebble install`. You should see the watchface now uses Jersey - the same distinctive font from the C tutorial - with the time and date properly centered as a block. ## C vs. Alloy Custom Fonts | | C SDK | Alloy | | --- | --- | --- | | **Font file** | `.ttf` in `resources/fonts/` | `.ttf` in `src/embeddedjs/assets/` | | **Declaration** | `package.json` resources array | `manifest.json` `*-alpha` resources | | **Loading** | `fonts_load_custom_font()` | `parseBMF()` + `parseRLE()` | | **Character subsetting** | All glyphs included | Specify `blocks` or `characters` | | **Cleanup** | `fonts_unload_custom_font()` | Automatic (garbage collected) | ## Experimenting Here are some things you can try: - Switch to a different TTF font - any TrueType font works. - Change the font sizes to see how the layout adapts. - Add a third font size for a different text element. - Try different `blocks` values like `"Latin Extended-A"` for accented characters. > **Tip** : Use `"monochrome": true` for sharp text on Pebble's display. Omit it for anti-aliased rendering on higher-color displays. ## Conclusion In this part we learned how to: 1. Add a custom TrueType font to an Alloy project. 2. Declare font resources in `manifest.json` with character subsetting. 3. Load custom fonts with `parseBMF` and `parseRLE`. 4. Center the time+date block vertically using font metrics. 5. Precompute layout positions for better performance. Your watchface now has the same distinctive Jersey look as the C tutorial. Check your code against [the source for this part](https://github.com/coredevices/alloy-watchface-tutorial/tree/main/part2) if you run into any issues. ## What's Next? In the next part we will add a battery meter and connection disconnect alerts to give users useful information at a glance. [Go to Part 3 →](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part3.md) --- # Battery Meter and Connection Alerts Source: https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part3/ # Battery Meter and Connection Alerts This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) Our watchface tells the time with style, but a great watchface also gives useful information at a glance. In this part we will add two popular features: a battery meter and a connection disconnect alert. By the end of this part, your watchface will look something like this: #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part3~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part3~gabbro.png) This section continues from [_Part 2_](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part2.md), so be sure to re-use your code or start with that finished project. ## The Battery Meter ### Reading Battery State In Alloy, battery information comes from the `Battery` sensor. Import it at the top of your file: ``` import Battery from "embedded:sensor/Battery"; ``` Create a `Battery` instance with an `onSample` callback that fires whenever the battery state changes: ``` let batteryPercent = 100;const battery = new Battery({onSample() {batteryPercent = this.sample().percent;drawScreen();}});batteryPercent = battery.sample().percent; ``` `battery.sample()` returns an object with `percent` (0–100), `charging` (boolean), and `plugged` (boolean). We call `sample()` once at startup to get the initial value, and the `onSample` callback handles updates. Each update triggers a full redraw. ### Drawing the Battery Bar We will draw the battery bar near the top of the screen. It has a white border with a filled portion that changes color based on the charge level - green when healthy, yellow when getting low, red when critical. Add a `drawBatteryBar()` function: ``` const green = render.makeColor(0, 170, 0);const yellow = render.makeColor(255, 170, 0);const red = render.makeColor(255, 0, 0);function drawBatteryBar() {const barWidth = (render.width / 2) | 0;const barX = ((render.width - barWidth) / 2) | 0;const barY = render.height \< 180 ? 6 : 20;const barHeight = 8;// Draw borderrender.fillRectangle(white, barX, barY, barWidth, barHeight);render.fillRectangle(black, barX + 1, barY + 1, barWidth - 2, barHeight - 2);// Choose color based on battery levellet barColor;if (batteryPercent \<= 20) {barColor = red;} else if (batteryPercent \<= 40) {barColor = yellow;} else {barColor = green;}// Draw filled portionconst fillWidth = ((batteryPercent \* (barWidth - 4)) / 100) | 0;render.fillRectangle(barColor, barX + 2, barY + 2, fillWidth, barHeight - 4);} ``` The `| 0` trick truncates floating-point results to integers, which is faster than `Math.floor()` on an embedded device. We position the bar differently based on screen height (`render.height < 180`) so it looks good on both Emery and Gabbro. Call `drawBatteryBar()` inside `draw()`, after clearing the background. ## Connection Disconnect Alert ### Monitoring Connection State Alloy provides connection status through the `watch.connected` object. We can listen for changes with the `connected` event: ``` let isConnected = true;function checkConnection() {isConnected = watch.connected.app;drawScreen();}watch.addEventListener("connected", checkConnection);checkConnection(); ``` `watch.connected.app` is `true` when the watch is connected to the phone app, `false` when disconnected. We check immediately at startup and on every change. ### Showing the Disconnect Indicator When the connection is lost, we draw a red "X" below the battery bar. Add this to your draw function after the battery bar: ``` // Draw disconnect indicator below battery barif (!isConnected) {const btStr = "X";const btWidth = render.getTextWidth(btStr, smallFont);const btY = render.height \< 180 ? 16 : 30;render.drawText(btStr, smallFont, red,(render.width - btWidth) / 2, btY);} ``` You will need the `smallFont` for this - add it with your other font declarations: ``` const smallFont = new render.Font("Gothic-Regular", 18); ``` ## Updating the Draw Function Since battery and connection changes now trigger redraws outside of time events, we need to handle the case where `draw()` is called without an event. Rename it to `drawScreen()` and add a fallback for the date: ``` let lastDate = new Date();function drawScreen(event) {const now = event?.date ?? lastDate;if (event?.date) lastDate = event.date;// ... rest of draw code} ``` The `event?.date` optional chaining returns `undefined` if `event` is missing or has no `date`. The `??` nullish coalescing operator falls back to `lastDate`. We save the latest date so non-time redraws still show the correct time. Update the event listener to use the new name: ``` watch.addEventListener("minutechange", drawScreen); ``` ## Testing in the Emulator Click the **play** button to compile and install your watchface in the CloudPebble emulator. Build and install your watchface: ``` $ pebble build && pebble install --emulator emery ``` ### Setting the Battery Level In the CloudPebble emulator, use the gear menu to adjust the battery level. Use `pebble emu-battery` to change the simulated battery level: ``` $ pebble emu-battery --percent 80$ pebble emu-battery --percent 30$ pebble emu-battery --percent 10 ``` You should see the bar go from green to yellow to red as the level decreases. ### Toggling the Connection In the CloudPebble emulator, use the gear menu to toggle the connection on and off. Use `pebble emu-bt-connection` to simulate a disconnect: ``` $ pebble emu-bt-connection --connected no$ pebble emu-bt-connection --connected yes ``` When disconnected, the red "X" should appear below the battery bar. ## Conclusion In this part we learned how to: 1. Import and use the `Battery` sensor for charge level updates. 2. Draw a color-coded battery bar using Poco primitives. 3. Monitor connection status with `watch.connected` and the `connected` event. 4. Show a disconnect indicator. 5. Handle redraws triggered by non-time events. Your watchface now shows the battery level and alerts you when the phone disconnects. Check your code against [the source for this part](https://github.com/coredevices/alloy-watchface-tutorial/tree/main/part3). ## What's Next? In the next part we will add weather information by fetching data from the Open-Meteo API - our first foray into network communication. [Go to Part 4 →](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part4.md) --- # Adding Weather with Open-Meteo Source: https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part4/ # Adding Weather with Open-Meteo This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) Up until now, everything in our watchface has been running entirely on the watch. In this part we take a big step: communicating with the phone to fetch live weather data from the web. We will use the `Location` sensor to get the user's GPS coordinates, then the standard `fetch()` API to get weather data from the free [Open-Meteo](https://open-meteo.com) API (no API key needed!). By the end, our watchface will show the current temperature and weather conditions: #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part4~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part4~gabbro.png) This section continues from [_Part 3_](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part3.md). ## How Networking Works in Alloy Alloy apps make HTTP requests using the `fetch()` API. Requests are proxied through PebbleKit JS (PKJS) running on the phone. This means you need: 1. The `@moddable/pebbleproxy` package installed 2. A phone connected to your Pebble with internet access ``` ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ │ Watch │ │ Phone (PKJS) │ │ Internet │ │ │ │ │ │ │ │ fetch() ──┼── msg ──>│ pebbleproxy ──┼── HTTP ─>│ API server │ │ │ │ │ │ │ │ <── msg ───┼──────────┤ <── response ───┼──────────┤ │ │ │ │ │ │ │ │ Location ──┼── msg ──>│ → GPS lookup │ │ │ │ sensor │ │ → sends coords │ │ │ │ <── msg ───┼──────────┤ back to watch │ │ │ └─────────────┘ └──────────────────┘ └──────────────┘ ``` ## Setting Up the Network Proxy In CloudPebble, go to the **Dependencies** section in the left sidebar and add `@moddable/pebbleproxy` as a NPM Dependency. Install the `@moddable/pebbleproxy` package: ``` $ pebble package install @moddable/pebbleproxy ``` Click **Add New** next to **Source Files** , select **JavaScript file** , and name it `index.js`. Set up the proxy: Create (or update) `src/pkjs/index.js` to set up the proxy: ``` const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', moddableProxy.readyReceived);Pebble.addEventListener('appmessage', moddableProxy.appMessageReceived); ``` That's it! The proxy handles forwarding `fetch()` requests and Location sensor data between the watch and phone. No custom location code is needed in PKJS. ## Adding the Location Capability In CloudPebble, go to **Settings** and check **Uses Location** so the phone is allowed to access GPS. Add the `location` capability to `package.json` so the phone is allowed to access GPS: ``` "capabilities": ["location"] ``` ## Preparing the Layout We need a weather display at the bottom of the screen. Add a weather variable near the top of `main.js`: ``` let weather = null; ``` In `drawScreen()`, add the weather display after the date, before `render.end()`: ``` // Draw weather at bottomconst weatherY = render.height - smallFont.height -(render.height \< 180 ? 6 : 20);if (weather) {const weatherStr = `${weather.temp}°C ${weather.conditions}`;width = render.getTextWidth(weatherStr, smallFont);render.drawText(weatherStr, smallFont, white,(render.width - width) / 2, weatherY);} else {const msg = "Loading...";width = render.getTextWidth(msg, smallFont);render.drawText(msg, smallFont, white,(render.width - width) / 2, weatherY);} ``` ## Getting the User's Location Alloy provides a `Location` sensor that follows the same ECMA-419 pattern as the Battery sensor we used in Part 3. Import it at the top of `main.js`: ``` import Location from "embedded:sensor/Location"; ``` Create a `requestLocation()` function that opens a `Location` sensor. The `onSample` callback fires once the phone has obtained the GPS coordinates: ``` let location = null;function requestLocation() {location = new Location({onSample() {const sample = this.sample();console.log("Got location: " + sample.latitude + ", " + sample.longitude);this.close();fetchWeather(sample.latitude, sample.longitude);}});} ``` A few important things to note: - **`this.close()`** - unlike Battery, we close the Location sensor after getting the first reading. Location is a one-shot request, not a continuous monitor. We wrap it in a function so we can request a fresh location each time we want to refresh the weather. - **Coordinates come as floats** - `sample.latitude` and `sample.longitude` are standard decimal degree values (e.g., `37.7749`, `-122.4194`). No need to multiply or divide by 10000 like in the C tutorial. - **The proxy handles everything** - the `@moddable/pebbleproxy` package handles the phone-side GPS lookup automatically. You don't need any custom PKJS code for location. Compare this to the C approach where you need custom PKJS code with `navigator.geolocation`, `sendAppMessage`, integer coordinate encoding, `AppMessage` handlers, and `messageKeys` in `package.json`. With the Location sensor, it's just a few lines. ## Fetching Weather Data Add a function to convert Open-Meteo weather codes: ``` function getWeatherDescription(code) {if (code === 0) return "Clear";if (code \<= 3) return "Cloudy";if (code \<= 48) return "Fog";if (code \<= 55) return "Drizzle";if (code \<= 57) return "Fz. Drizzle";if (code \<= 65) return "Rain";if (code \<= 67) return "Fz. Rain";if (code \<= 75) return "Snow";if (code \<= 77) return "Snow Grains";if (code \<= 82) return "Showers";if (code \<= 86) return "Snow Shwrs";if (code === 95) return "T-Storm";if (code \<= 99) return "T-Storm";return "Unknown";} ``` Now the fetch function itself. Notice it takes `latitude` and `longitude` as parameters - the Location sensor passes them directly: ``` async function fetchWeather(latitude, longitude) {try {const url = new URL("http://api.open-meteo.com/v1/forecast");url.search = new URLSearchParams({latitude,longitude,current: "temperature\_2m,weather\_code"});console.log("Fetching weather...");const response = await fetch(url);const data = await response.json();weather = {temp: Math.round(data.current.temperature\_2m),conditions: getWeatherDescription(data.current.weather\_code)};console.log("Weather: " + weather.temp + "C, " + weather.conditions);drawScreen();} catch (e) {console.log("Weather fetch error: " + e);}} ``` > **Why Open-Meteo?** Unlike many weather APIs, Open-Meteo is completely free and requires no API key. The URL is simple and the response is clean JSON. Notice how clean this is compared to the C approach: no `XMLHttpRequest`, no manual JSON parsing callbacks. Alloy's `fetch()` with `async`/`await` makes the code straightforward. ## Automatic Refresh To keep the weather current, trigger a refresh every hour using the `hourchange` event. This calls `requestLocation()`, which creates a new `Location` instance, gets fresh GPS coordinates, and chains into a weather fetch: ``` watch.addEventListener("hourchange", requestLocation); ``` Like `minutechange`, the `hourchange` event fires immediately when registered, so this also triggers the initial weather fetch at startup. ## Conclusion In this part we learned how to: 1. Set up the `@moddable/pebbleproxy` package for network access. 2. Use the `Location` sensor to get GPS coordinates from the phone. 3. Use `fetch()` with `async`/`await` to call a web API. 4. Parse the Open-Meteo JSON response. 5. Display weather data on the watchface. 6. Set up automatic refresh via a time event. Your watchface now shows live weather data! Check your code against [the source for this part](https://github.com/coredevices/alloy-watchface-tutorial/tree/main/part4). ## What's Next? In the next part we will add support for Timeline Quick View - adapting the layout when the screen is partially obstructed. [Go to Part 5 →](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part5.md) --- # Timeline Peek Source: https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part5/ # Timeline Peek This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) Pebble's Timeline Quick View can appear at the bottom of the screen to show upcoming events. When it does, it covers part of your watchface. In this part we will use the unobstructed area dimensions to gracefully adapt our layout - repositioning the time, date, and weather to fit in the remaining space. #### emery ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part5~emery.gif) This section continues from [_Part 4_](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part4.md). ## How Timeline Quick View Works Timeline Quick View is a system overlay that obstructs the bottom ~51 pixels of the screen (including a 2px border). Your watchface is not required to handle this, but adapting your layout gives users a better experience - the time, date, and weather stay visible instead of being hidden behind the overlay. > **Note** : Timeline Quick View is not currently supported for the Round platforms (gabbro), but writing compatible code ensures your watchface handles it gracefully on all platforms. ## Using the Unobstructed Area In the C SDK you would use `layer_get_unobstructed_bounds()` and subscribe to three separate event handlers. In Alloy, the Poco renderer exposes the unobstructed dimensions directly: - `render.unobstructed.width` - width of the area not covered by a system overlay - `render.unobstructed.height` - height of the area not covered by a system overlay When there is no overlay, these are identical to `render.width` and `render.height`. When Timeline Quick View is active, `render.unobstructed.height` is smaller. The key rule is simple: use `render.width`/`render.height` for clearing the full screen, and `render.unobstructed.width`/`render.unobstructed.height` for positioning content. ## Listening for Resize Events When the overlay appears or disappears, the system fires a `resize` event. We just need to redraw: ``` watch.addEventListener("resize", drawScreen); ``` That is all. Unlike the C SDK where you subscribe to three separate callbacks (`.will_change`, `.change`, `.did_change`), Alloy fires a single `resize` event and you simply redraw using the current unobstructed dimensions. ## Updating the Layout ### Moving layout constants into drawScreen() In Part 4, we precomputed the vertical positions at the top of the file: ``` // Remove these top-level constants:const blockHeight = timeFont.height + dateFont.height;const timeY = (render.height - blockHeight) / 2;const dateY = timeY + timeFont.height; ``` These values were computed once at startup using `render.height`. Since the unobstructed height can change at any time, we need to recompute them on every draw. Move them inside `drawScreen()` and use `render.unobstructed.height`: ``` function drawScreen(event) {const now = event?.date ?? lastDate;if (event?.date) lastDate = event.date;render.begin();render.fillRectangle(black, 0, 0, render.width, render.height);// Compute layout positions from unobstructed areaconst blockHeight = timeFont.height + dateFont.height;const timeY = (render.unobstructed.height - blockHeight) / 2;const dateY = timeY + timeFont.height;// ... rest of drawing code ... ``` Notice that the background fill still uses `render.width`/`render.height` to clear the entire screen, but `timeY` and `dateY` are now based on `render.unobstructed.height` so the time and date stay centered in the visible area. ### Updating drawBatteryBar() Replace `render.width` and `render.height` with their unobstructed equivalents so the battery bar stays within the visible area: ``` function drawBatteryBar() {const barWidth = (render.unobstructed.width / 2) | 0;const barX = ((render.unobstructed.width - barWidth) / 2) | 0;const barY = render.unobstructed.height \< 180 ? 6 : 20;const barHeight = 8;// ... rest unchanged ...} ``` ### Updating element positions in drawScreen() Every element that was positioned with `render.width` or `render.height` now uses the unobstructed dimensions instead: ``` // Bluetooth indicatorif (!isConnected) {const btStr = "X";const btWidth = render.getTextWidth(btStr, smallFont);const btY = render.unobstructed.height \< 180 ? 16 : 30;render.drawText(btStr, smallFont, red,(render.unobstructed.width - btWidth) / 2, btY);}// Time - centered horizontally in unobstructed arealet width = render.getTextWidth(timeStr, timeFont);render.drawText(timeStr, timeFont, white,(render.unobstructed.width - width) / 2, timeY);// Date - centered horizontally in unobstructed areawidth = render.getTextWidth(dateStr, dateFont);render.drawText(dateStr, dateFont, white,(render.unobstructed.width - width) / 2, dateY);// Weather - positioned from bottom of unobstructed areaconst weatherY = render.unobstructed.height - smallFont.height -(render.unobstructed.height \< 180 ? 6 : 20); ``` ## Alloy vs. C | | C (UnobstructedArea API) | Alloy | | --- | --- | --- | | **Getting dimensions** | `layer_get_unobstructed_bounds()` | `render.unobstructed.width`/`.height` | | **Event subscription** | Three handlers: `.will_change`, `.change`, `.did_change` | Single `resize` event | | **Animation support** | `.change` fires during animation for smooth transitions | `resize` fires once; redraw with new dimensions | | **Startup handling** | Must manually call handlers for initial state | Dimensions are always current | | **Code changes** | Subscribe to service, implement 3 callbacks, store layer reference | Replace `render.width`/`.height` with `render.unobstructed.*`, add one event listener | ## Testing Timeline Quick View You can toggle Timeline Quick View using the emulator controls in CloudPebble. You can toggle Timeline Quick View in the emulator: ``` $ pebble emu-set-timeline-quick-view on$ pebble emu-set-timeline-quick-view off ``` When enabled, you should see the time, date, and weather squeeze together into the remaining space. When disabled, everything returns to its original position. ## Conclusion In this part we learned how to: 1. Use `render.unobstructed.width` and `render.unobstructed.height` to find the available screen space. 2. Listen for `resize` events to redraw when the unobstructed area changes. 3. Move precomputed layout positions into the draw function so they update dynamically. 4. Keep the background fill at full screen size while positioning content in the unobstructed area. Your watchface now adapts gracefully to Timeline Quick View. Check your code against [the source for this part](https://github.com/coredevices/alloy-watchface-tutorial/tree/main/part5). ## What's Next? In the next part we will add user settings - letting users choose colors, toggle the date display, and pick temperature units - all persisted with `localStorage`. [Go to Part 6 →](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part6.md) --- # User Settings with localStorage and Clay Source: https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part6/ # User Settings with localStorage and Clay This page contains some instructions that are different if you're using CloudPebble or if you're using the SDK locally on your computer. Select whether you're using CloudPebble or the SDK below to show the relevant instructions! [![](https://developer.repebble.com/assets/images/sdk/cloud.svg) #### CloudPebble ](javascript:void();)[![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) #### SDK ](javascript:void();) ![](https://developer.repebble.com/assets/images/sdk/cloud.svg) Showing instructions for CloudPebble. [Not using CloudPebble?](javascript:%20void();) ![](https://developer.repebble.com/assets/images/sdk/sdk-box.svg) Showing instructions for the SDK. [Using CloudPebble?](javascript:%20void();) The finishing touch for any great watchface is letting users make it their own. In this final part we will add user settings using `localStorage` - a simple key-value store built right into Alloy - and then add a phone-side configuration page using [Clay for Pebble](https://github.com/pebble-dev/clay) so users can change settings from their phone. Users will be able to choose background and text colors, toggle the date display, pick temperature units, and choose between 12-hour and 24-hour time formats. Here is an example of a customized watchface: #### emery #### gabbro ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part6~emery.png) ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part6~gabbro.png) This section continues from [_Part 5_](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part5.md). ## How localStorage Works in Alloy Alloy provides the standard Web `localStorage` API for persistent storage. Data is saved to flash and survives app restarts. The API is straightforward: - `localStorage.setItem(key, value)` - store a string value - `localStorage.getItem(key)` - retrieve a value (or `null` if missing) - `localStorage.removeItem(key)` - delete a value Since `localStorage` only stores strings, we use `JSON.stringify()` and `JSON.parse()` to store structured data like our settings object. ## Defining Default Settings Add a defaults object near the top of `main.js`, after the font declarations: ``` const DEFAULT\_SETTINGS = {backgroundColor: { r: 0, g: 0, b: 0 },textColor: { r: 255, g: 255, b: 255 },useFahrenheit: false,showDate: true,use24Hour: true}; ``` We store colors as `{ r, g, b }` objects so they serialize cleanly to JSON. ## Loading and Saving Settings Add two helper functions: ``` function loadSettings() {const stored = localStorage.getItem("settings");if (stored) {try {return { ...DEFAULT\_SETTINGS, ...JSON.parse(stored) };} catch (e) {console.log("Failed to parse settings");}}return { ...DEFAULT\_SETTINGS };}function saveSettings() {localStorage.setItem("settings", JSON.stringify(settings));}let settings = loadSettings(); ``` The spread pattern `{ ...DEFAULT_SETTINGS, ...JSON.parse(stored) }` is important. It ensures that if we add new settings in a future version, they automatically get default values even when loading old saved data. This is the Alloy equivalent of the C pattern of calling `prv_default_settings()` before `persist_read_data()`. ## Creating Colors from Settings Replace the hardcoded `black` and `white` color declarations with settings-driven ones: ``` let bgColor = render.makeColor(settings.backgroundColor.r,settings.backgroundColor.g, settings.backgroundColor.b);let textColor = render.makeColor(settings.textColor.r,settings.textColor.g, settings.textColor.b);function updateColors() {bgColor = render.makeColor(settings.backgroundColor.r,settings.backgroundColor.g, settings.backgroundColor.b);textColor = render.makeColor(settings.textColor.r,settings.textColor.g, settings.textColor.b);} ``` The `updateColors()` function recreates the Poco color values whenever settings change. Call it after loading new settings. ## Applying Settings to the Display Update `drawScreen()` to use the settings-driven colors and respect the `showDate` toggle: ``` function drawScreen(event) {const now = event?.date ?? lastDate;if (event?.date) lastDate = event.date;render.begin();render.fillRectangle(bgColor, 0, 0, render.width, render.height);// ... battery bar and disconnect indicator ...// Format time as HH:MM (24h) or H:MM (12h)let hours = now.getHours();if (!settings.use24Hour) {hours = hours % 12 || 12;}const hoursStr = String(hours).padStart(2, "0");const minutes = String(now.getMinutes()).padStart(2, "0");const timeStr = `${hoursStr}:${minutes}`;// Draw timelet width = render.getTextWidth(timeStr, timeFont);render.drawText(timeStr, timeFont, textColor,(render.width - width) / 2, timeY);// Draw date if setting is enabledif (settings.showDate) {// ... date drawing code ...render.drawText(dateStr, dateFont, textColor,(render.width - width) / 2, dateY);}// ... weather display ...render.end();} ``` The key changes: - `bgColor` replaces `black` for the background fill - `textColor` replaces `white` for all text drawing - The date block is wrapped in `if (settings.showDate)` - Time formatting respects the `use24Hour` setting - when disabled, hours are converted to 12-hour format using `hours % 12 || 12` (the `|| 12` turns midnight's `0` into `12`) Also update the battery bar border to use `textColor` instead of hardcoded white, so it matches the user's color choice: ``` function drawBatteryBar() {// ...render.fillRectangle(textColor, barX, barY, barWidth, barHeight);render.fillRectangle(bgColor, barX + 1, barY + 1, barWidth - 2, barHeight - 2);// ...} ``` ## Temperature Unit Support Update the weather display to show the correct unit: ``` if (weather) {const unit = settings.useFahrenheit ? "F" : "C";const weatherStr = `${weather.temp}°${unit} ${weather.conditions}`;// ...} ``` In `fetchWeather()`, pass the unit preference to Open-Meteo so the API returns the temperature in the correct unit directly: ``` async function fetchWeather(latitude, longitude) {// ...const params = {latitude,longitude,current: "temperature\_2m,weather\_code"};if (settings.useFahrenheit) {params.temperature\_unit = "fahrenheit";}const url = new URL("https://api.open-meteo.com/v1/forecast");url.search = new URLSearchParams(params);// ...} ``` Open-Meteo supports a `temperature_unit` parameter natively, so we do not need to convert on the watch - the API handles it for us. ## Caching Weather Data Since weather data is expensive to fetch (it requires a phone connection and network request), we cache it in `localStorage` so the watchface can show recent weather immediately on startup: ``` function loadCachedWeather() {const cached = localStorage.getItem("weather");const cachedTime = localStorage.getItem("weatherTime");if (cached && cachedTime) {const age = Date.now() - Number(cachedTime);// Use cache if less than 1 hour oldif (age \< 60 \* 60 \* 1000) {try {weather = JSON.parse(cached);console.log("Using cached weather");return true;} catch (e) {console.log("Failed to parse cached weather");}}}return false;}function saveWeather() {if (weather) {localStorage.setItem("weather", JSON.stringify(weather));localStorage.setItem("weatherTime", String(Date.now()));}} ``` Call `loadCachedWeather()` at startup, and call `saveWeather()` at the end of a successful `fetchWeather()`: ``` // At module levelloadCachedWeather();// Inside fetchWeather(), after setting weather:saveWeather();drawScreen(); ``` This gives users instant weather display on app launch instead of "Loading..." while waiting for the phone connection and API response. ## Adding a Settings Page with Clay So far our settings only have default values and no way for the user to change them. Let's add a phone-side configuration page using [Clay for Pebble](https://github.com/pebble-dev/clay). Clay generates a settings UI on the phone from a simple JSON definition and sends the values to the watch via AppMessage - the same mechanism used by the C tutorial. ## Installing Clay In CloudPebble, go to the **Dependencies** section in the left sidebar and add `@rebble/clay` as a NPM Dependency. Clay is available as a Pebble Package. Install it from your project directory: ``` $ pebble package install @rebble/clay ``` This adds `@rebble/clay` to the `dependencies` in `package.json`. ## Enabling Configuration In CloudPebble, go to **Settings** and tick the **Configurable** checkbox so the gear icon appears next to your watchface in the phone app. For the gear icon to appear next to your watchface in the phone app, add `configurable` to the `capabilities` array in `package.json`: ``` "capabilities": ["location","configurable"] ``` ## Defining Message Keys In CloudPebble, go to **Settings** and add the following message keys in the **PebbleKit JS Message Keys** section: `BackgroundColor`, `TextColor`, `TemperatureUnit`, `ShowDate`, and `HourFormat`. Clay sends settings to the watch as AppMessage key-value pairs. Add message keys for each setting to `package.json`: ``` "messageKeys": ["BackgroundColor","TextColor","TemperatureUnit","ShowDate","HourFormat"] ``` These keys are used by Clay in the PKJS layer and by the `Message` class on the watch to identify which setting is being sent. ## Creating the Clay Configuration Click **Add New** next to **Source Files** , select **JavaScript file** , and name it `config.js`. Clay uses a simple JSON array of sections and fields: Create `src/pkjs/config.js` with the configuration definition. Clay uses a simple JSON array of sections and fields: ``` module.exports = [{"type": "heading","defaultValue": "Watchface Settings"},{"type": "text","defaultValue": "Customize your watchface appearance and preferences."},{"type": "section","items": [{"type": "heading","defaultValue": "Colors"},{"type": "color","messageKey": "BackgroundColor","defaultValue": "0x000000","label": "Background Color"},{"type": "color","messageKey": "TextColor","defaultValue": "0xFFFFFF","label": "Text Color"}]},{"type": "section","items": [{"type": "heading","defaultValue": "Preferences"},{"type": "toggle","messageKey": "TemperatureUnit","label": "Use Fahrenheit","defaultValue": false},{"type": "toggle","messageKey": "ShowDate","label": "Show Date","defaultValue": true},{"type": "toggle","messageKey": "HourFormat","label": "Use 24-Hour Format","defaultValue": true}]},{"type": "submit","defaultValue": "Save Settings"}]; ``` This is the same configuration used in the C tutorial. Each `messageKey` matches a key in `package.json`. The `color` type provides a color picker and `toggle` gives a switch. ## Initializing Clay in PKJS Open your JavaScript file in the CloudPebble editor and initialize Clay before the proxy: Update `src/pkjs/index.js` to initialize Clay before the proxy: ``` var Clay = require('@rebble/clay');var clayConfig = require('./config');var clay = new Clay(clayConfig);const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', moddableProxy.readyReceived);Pebble.addEventListener('appmessage', moddableProxy.appMessageReceived); ``` Clay automatically handles the `showConfiguration` and `webviewClosed` events (opening the settings page and sending results back). The proxy continues to handle `ready` and `appmessage` for fetch and location requests from the watch. There is no conflict since they use different event types. ## Receiving Settings on the Watch When the user saves settings on the phone, Clay sends them as an AppMessage. On the watch side, we use the `Message` class from `pebble/message` to receive them. Add the import at the top of `main.js`: ``` import Message from "pebble/message"; ``` Then add a `Message` instance at the end of the file: ``` const message = new Message({keys: ["BackgroundColor", "TextColor", "TemperatureUnit", "ShowDate", "HourFormat"],onReadable() {const msg = this.read();const bg = msg.get("BackgroundColor");if (bg !== undefined) {settings.backgroundColor = { r: (bg \>\> 16) & 0xFF, g: (bg \>\> 8) & 0xFF, b: bg & 0xFF };}const tc = msg.get("TextColor");if (tc !== undefined) {settings.textColor = { r: (tc \>\> 16) & 0xFF, g: (tc \>\> 8) & 0xFF, b: tc & 0xFF };}const tu = msg.get("TemperatureUnit");if (tu !== undefined) {settings.useFahrenheit = tu === 1;}const sd = msg.get("ShowDate");if (sd !== undefined) {settings.showDate = sd === 1;}const hf = msg.get("HourFormat");if (hf !== undefined) {settings.use24Hour = hf === 1;}saveSettings();updateColors();drawScreen();// Re-fetch weather if temperature unit changedif (tu !== undefined) {requestLocation();}}}); ``` The `keys` array tells `Message` which AppMessage keys to listen for - these must match the `messageKeys` in `package.json`. Clay sends colors as `0x00RRGGBB` int32 values. We extract the red, green, and blue components with bit shifts: - Red: `(value >> 16) & 0xFF` - Green: `(value >> 8) & 0xFF` - Blue: `value & 0xFF` Toggles arrive as int32 values where `1` means on and `0` means off. After updating the settings object, we save to `localStorage`, recreate the Poco color values with `updateColors()`, and redraw the screen. If the temperature unit changed, we also re-fetch weather so the API returns values in the correct unit. ## Trying It Out Click the **play** button to compile and install, then tap the gear icon in the emulator to open the settings page. Build and install your watchface, then use `pebble emu-app-config` to open the settings page in your browser: ``` $ pebble build && pebble install --emulator emery$ pebble emu-app-config ``` Try changing the background color, text color, and toggling the date, temperature unit, and hour format - you should see the watchface update immediately. ![Settings page on phone](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6-settings.gif) ## Alloy vs. C Settings | | C (Clay) | Alloy (Clay + localStorage) | | --- | --- | --- | | **Settings UI** | Clay JSON config on phone | Same Clay JSON config on phone | | **Phone-side code** | Clay init in PKJS | Clay init + proxy in PKJS | | **Receiving settings** | `inbox_received_callback` with `dict_find` | `Message` class with `onReadable` | | **Storage** | `persist_write_data()` / `persist_read_data()` | `localStorage.setItem()` / `getItem()` | | **Serialization** | Raw struct bytes | JSON strings | | **New fields** | Must call defaults before load | Spread operator merges defaults | The phone-side Clay setup is identical between C and Alloy - the same `config.js` works for both. The difference is on the watch side: C uses `dict_find` in an inbox callback to extract values, while Alloy uses the `Message` class with a readable callback. ## Conclusion Congratulations! You have built a complete, feature-rich Pebble watchface in Alloy. Here is everything it includes: 1. **Digital time display** with the Jersey font and 12h/24h format option. 2. **Date display** that can be toggled on/off. 3. **Live weather** from Open-Meteo (no API key needed). 4. **Battery meter** with color-coded levels. 5. **Bluetooth disconnect** indicator. 6. **User settings** persisted with `localStorage`. 7. **Weather caching** for instant startup display. 8. **Clay configuration** for colors and preferences. In this final part we learned how to: - Use `localStorage` for persistent key-value storage. - Define default settings with a spread-merge pattern. - Apply color, display, and time format preferences dynamically. - Pass unit preferences to the Open-Meteo API. - Cache API responses for faster startup. - Install and configure Clay for a phone-side settings page. - Receive Clay settings on the watch with the `Message` class. - Parse color and toggle values from AppMessages. Check your code against [the source for this part](https://github.com/coredevices/alloy-watchface-tutorial/tree/main/part6). Now it is time to [_publish your watchface_](https://developer.repebble.com/guides/appstore-publishing.md) in the Pebble appstore. --- # Vector Animations Source: https://developer.repebble.com/tutorials/advanced/vector-animations/ # Vector Animations Some of the best Pebble apps make good use of the [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") and the [`Graphics Context`](https://developer.repebble.com/docs/c/Graphics.md "Graphics") to create beautiful and eye-catching user interfaces that look better than those created with just the standard [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") types. Taking a good design a step further may involve using the [`Draw Commands`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands") API to load vector icons and images, and to animate them on a point-by-point basis at runtime. An additional capability of the [`Draw Commands`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands") API is the draw command sequence, allowing multiple frames to be incorporated into a single resource and played out frame by frame. This tutorial will guide you through the process of using these types of image files in your own projects. ## What Are Vector Images? As opposed to bitmaps which contain data for every pixel to be drawn, a vector file contains only instructions about points contained in the image and how to draw lines connecting them up. Instructions such as fill color, stroke color, and stroke width are also included. Vector images on Pebble are implemented using the [`Draw Commands`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands") APIs, which load and display PDC (Pebble Draw Command) images and sequences that contain sets of these instructions. An example is the weather icon used in weather timeline pins. The benefit of using vector graphics for this icon is that is allows the image to stretch in the familiar manner as it moves between the timeline view and the pin detail view: ![weather](https://developer.repebble.com/assets/images/tutorials/advanced/weather.png) By including two or more vector images in a single file, an animation can be created to enable fast and detailed animated sequences to be played. Examples can be seen in the Pebble system UI, such as when an action is completed: ![action-completed](https://developer.repebble.com/assets/images/tutorials/advanced/action-completed.gif) The main benefits of vectors over bitmaps for simple images and icons are: - Smaller resource size - instructions for joining points are less memory expensive than per-pixel bitmap data. - Flexible rendering - vector images can be rendered as intended, or manipulated at runtime to move the individual points around. This allows icons to appear more organic and life-like than static PNG images. Scaling and distortion is also made possible. - Longer animations - a side benefit of taking up less space is the ability to make animations longer. However, there are also some drawbacks to choosing vector images in certain cases: - Vector files require more specialized tools to create than bitmaps, and so are harder to produce. - Complicated vector files may take more time to render than if they were simply drawn per-pixel as a bitmap, depending on the drawing implementation. ## Creating Compatible Files The file format of vector image files on Pebble is the PDC (Pebble Draw Command) format, which includes all the instructions necessary to allow drawing of vectors. These files are created from compatible SVG (Scalar Vector Graphics) files using the [`svg2pdc`](https://github.com/pebble-examples/cards-example/blob/master/tools/svg2pdc.py) tool. Pebble Draw Command files can only be used from app resources, and cannot be created at runtime. To convert an SVG file to a PDC image of the same name: ``` $ python svg2pdc.py image.svg ``` To create a PDCS (Pebble Draw Command Sequence) from individual SVG frames, specify the directory containing the frames with the `--sequence` flag when running `svg2pdc`: ``` $ ls frames/1.svg2.svg3.svg4.svg5.svg $ python svg2pdc.py --sequence frames/ ``` In the example above, this will create an output file in the `frames` directory called `frames.pdc` that contains draw command data for the complete animation. **Limitations** The `svg2pdc` tool currently supports SVG files that use **only** the following elements: `g`, `layer`, `path`, `rect`, `polyline`, `polygon`, `line`, `circle`. We recommend using Adobe Illustrator to create compatible SVG icons and images. For simplicity, compatible image and sequence files will be provided for you to use in your own project. ## Getting Started Begin a new project using `pebble new-project` and create a simple app that pushes a blank [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"), such as the example below: ``` #include \static Window \*s\_main\_window;static void main\_window\_load(Window \*window) {Layer \*window\_layer = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(window\_layer);}static void main\_window\_unload(Window \*window) {}static void init() {s\_main\_window = window\_create();window\_set\_window\_handlers(s\_main\_window, (WindowHandlers) {.load = main\_window\_load,.unload = main\_window\_unload,});window\_stack\_push(s\_main\_window, true);}static void deinit() {window\_destroy(s\_main\_window);}int main() {init();app\_event\_loop();deinit();} ``` ## Drawing a PDC Image For this tutorial, use the example [`weather_image.pdc`](https://developer.repebble.com/assets/other/weather_image.pdc) file provided. Add the PDC file to your project resources in `package.json` as shown below. Set the 'name' field to `WEATHER_IMAGE`, and the 'type' field to `raw`. The file is assumed to be called `weather_image.pdc`: ``` "media": [{"type": "raw","name": "WEATHER\_IMAGE","file": "weather\_image.pdc"}] ``` Drawing a Pebble Draw Command image is just as simple as drawing a normal PNG image to a graphics context, requiring only one draw call. First, load the `.pdc` file from resources, for example with the `name` defined as `WEATHER_IMAGE`, as shown below. Declare a pointer of type [`GDrawCommandImage`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage "GDrawCommandImage") at the top of the file: ``` static GDrawCommandImage \*s\_command\_image; ``` Create and assign the [`GDrawCommandImage`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage "GDrawCommandImage") in `init()`, before calling `window_stack_push()`: ``` static void init() {/\* ... \*/// Create the object from resource files\_command\_image = gdraw\_command\_image\_create\_with\_resource(RESOURCE\_ID\_WEATHER\_IMAGE);/\* ... \*/} ``` Next, define the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") that will be used to draw the PDC image: ``` static void update\_proc(Layer \*layer, GContext \*ctx) {// Set the origin offset from the context for drawing the imageGPoint origin = GPoint(10, 20);// Draw the GDrawCommandImage to the GContextgdraw\_command\_image\_draw(ctx, s\_command\_image, origin);} ``` Next, create a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") to display the image: ``` static Layer \*s\_canvas\_layer; ``` Next, set the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") that will do the rendering and add it to the desired [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"): ``` static void main\_window\_load(Window \*window) {/\* ... \*/// Create the canvas Layers\_canvas\_layer = layer\_create(GRect(30, 30, bounds.size.w, bounds.size.h));// Set the LayerUpdateProclayer\_set\_update\_proc(s\_canvas\_layer, update\_proc);// Add to parent Windowlayer\_add\_child(window\_layer, s\_canvas\_layer);} ``` Finally, don't forget to free the memory used by the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")'s sub-components in `main_window_unload()`: ``` static void main\_window\_unload(Window \*window) {layer\_destroy(s\_canvas\_layer);gdraw\_command\_image\_destroy(s\_command\_image);} ``` When run, the PDC image will be loaded, and rendered in the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"). To put the image into contrast, we will finally change the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") background color after `window_create()`: ``` window\_set\_background\_color(s\_main\_window, GColorBlueMoon); ``` The result will look similar to the example shown below. ![weather-image](https://developer.repebble.com/assets/images/tutorials/advanced/weather-image.png) ## Playing a PDC Sequence The [`GDrawCommandSequence`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence "GDrawCommandSequence") API allows developers to use vector graphics as individual frames in a larger animation. Just like [`GDrawCommandImage`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage "GDrawCommandImage")s, each [`GDrawCommandFrame`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame "GDrawCommandFrame") is drawn to a graphics context in a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"). For this tutorial, use the example [`clock_sequence.pdc`](https://developer.repebble.com/assets/other/clock_sequence.pdc) file provided. Begin a new app, with a C file containing the [template](#getting-started) provided above. Next, add the file as a `raw` resource in the same way as for a PDC image, for example with the `name` field specified in `package.json` as `CLOCK_SEQUENCE`. ``` "media": [{"type": "raw","name": "CLOCK\_SEQUENCE","file": "clock\_sequence.pdc"}] ``` Load the PDCS in your app by first declaring a [`GDrawCommandSequence`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence "GDrawCommandSequence") pointer: ``` static GDrawCommandSequence \*s\_command\_seq; ``` Next, initialize the object in `init()` before calling `window_stack_push()`: ``` static void init() {/\* ... \*/// Load the sequences\_command\_seq = gdraw\_command\_sequence\_create\_with\_resource(RESOURCE\_ID\_CLOCK\_SEQUENCE);/\* ... \*/} ``` Get the next frame and draw it in the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"). Then register a timer to draw the next frame: ``` // Milliseconds between frames#define DELTA 13static int s\_index = 0;/\* ... \*/static void next\_frame\_handler(void \*context) {// Draw the next framelayer\_mark\_dirty(s\_canvas\_layer);// Continue the sequenceapp\_timer\_register(DELTA, next\_frame\_handler, NULL);}static void update\_proc(Layer \*layer, GContext \*ctx) {// Get the next frameGDrawCommandFrame \*frame = gdraw\_command\_sequence\_get\_frame\_by\_index(s\_command\_seq, s\_index);// If another frame was found, draw itif (frame) {gdraw\_command\_frame\_draw(ctx, s\_command\_seq, frame, GPoint(0, 30));}// Advance to the next frame, wrapping if neccessaryint num\_frames = gdraw\_command\_sequence\_get\_num\_frames(s\_command\_seq);s\_index++;if (s\_index == num\_frames) {s\_index = 0;}} ``` Next, create a new [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") to utilize the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") and add it to the desired [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). Create the `Window` pointer: ``` static Layer \*s\_canvas\_layer; ``` Next, create the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") and assign it to the new pointer. Set its update procedure and add it to the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"): ``` static void main\_window\_load(Window \*window) {// Get Window informationLayer \*window\_layer = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(window\_layer);// Create the canvas Layers\_canvas\_layer = layer\_create(GRect(30, 30, bounds.size.w, bounds.size.h));// Set the LayerUpdateProclayer\_set\_update\_proc(s\_canvas\_layer, update\_proc);// Add to parent Windowlayer\_add\_child(window\_layer, s\_canvas\_layer);} ``` Start the animation loop using a timer at the end of initialization: ``` // Start the animationapp\_timer\_register(DELTA, next\_frame\_handler, NULL); ``` Finally, remember to destroy the [`GDrawCommandSequence`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence "GDrawCommandSequence") and [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") in `main_window_unload()`: ``` static void main\_window\_unload(Window \*window) {layer\_destroy(s\_canvas\_layer);gdraw\_command\_sequence\_destroy(s\_command\_seq);} ``` When run, the animation will be played by the timer at a framerate dictated by `DELTA`, looking similar to the example shown below: ![pdcs-example](https://developer.repebble.com/assets/images/tutorials/advanced/pdcs-example.gif) ## What's Next? You have now learned how to add vector images and animations to your apps. Complete examples for these APIs are available under the `pebble-examples` GitHub organization: - [`pdc-image`](https://github.com/pebble-examples/pdc-image) - Example implementation of a Pebble Draw Command Image. - [`pdc-sequence`](https://github.com/pebble-examples/pdc-sequence) - Example implementation of a Pebble Draw Command Sequence animated icon. More advanced tutorials will be added here in the future, so keep checking back! --- # Use CloudPebble Source: https://developer.repebble.com/sdk/cloud/ CloudPebble is the fastest way to get started building Pebble apps and watchfaces - no installation required! It runs entirely in your browser. [Go to CloudPebble](https://cloudpebble.repebble.com) ## What's included CloudPebble is a browser-based IDE with the Pebble SDK built in. Write code, compile, and install apps on your watch or the emulator from your browser. - **Code editor** with code completion, syntax error checking, and inline API documentation - **Built-in compiler** - one-click build with errors linked to source lines - **Emulator** - run your app on a virtual Pebble watch in the browser or on a server - **On-device install** - install on your watch through the Pebble mobile app - **Resource management** - manage images, fonts, and data files through a graphical interface - **GitHub integration** - import from, pull from and push to GitHub repositories - **Package support** - add dependencies from the npm package registry - **Publishing** - submit apps to the Pebble appstore from the IDE ## Signing in CloudPebble uses your Pebble account, the same account as the Pebble mobile app. Click **Log in with your Pebble account** and sign in with Google, GitHub or Apple. The account is also used for the Developer Connection and for publishing, so no further sign-in is needed for those. ## Project types The **Create New Project** dialog offers three project types: - **Pebble C SDK** - the primary language for Pebble apps and watchfaces. Builds for every platform. - **JavaScript SDK (beta)** - [Alloy](https://developer.repebble.com/guides/alloy.md), a JavaScript framework based on the Moddable SDK, with the Piu UI framework for component-based layouts and the Poco renderer for custom graphics. This is the default type. Alloy projects build for Pebble Time 2 (emery) and Pebble Round 2 (gabbro) only; the other platforms are not offered in the project settings. - **Pebble Package** - a reusable library of C code, JavaScript and resources that other projects can depend on. See the [Pebble Packages guides](https://developer.repebble.com/guides/pebble-packages.md). The **Template** list fills a new project with a sample app, an empty project, or one of the SDK demos. JavaScript SDK projects have their own template list. Project settings such as the app kind, target platforms, UUID, capabilities and message keys are edited in the **Settings** pane. CloudPebble generates `package.json` from these settings; the file itself cannot be edited. ## Importing a project Click **Import** on the project list to bring in an existing project: - **Upload Zip** - a zip file containing a standard Pebble project with its `package.json` or `appinfo.json`. - **Import from GitHub** - enter the repository URL and optionally a branch. Public repositories need no GitHub sign-in. Tick **Use as Git remote** to keep the project linked for pull and push, which does need the **GitHub Repo Sync** link in your account settings. A link of the form `https://cloudpebble.repebble.com/ide/import/github//` opens the import dialog prefilled, and `https://cloudpebble.repebble.com/ide/gist/` creates a project from a GitHub gist. Use these to share a project from a README. ## Building and running **Build & Run** in the sidebar compiles the project. Under **Run On** , pick **Emulator** and one of the seven platforms, or **Phone** to install on a connected watch through the [Developer Connection](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md). **View app logs** streams the app's log output and **Screenshot** saves an image of the watch display. The emulator can run in two places. The **Emulator** option in your account settings chooses between **In browser (fast, no server)** and **Cloud (server QEMU)**. The in-browser emulator starts faster and supports all platforms, screenshots, app logs and the sensor controls. It always uses your computer's clock, so the time cannot be changed, and audio only plays for emery and flint. The cloud emulator is the default. ## Timeline The **Timeline (Preview)** pane inserts a timeline pin into the emulator. Paste the pin JSON, which needs `id`, `time` and `layout`, and click **Insert pin**. **Delete pin** removes it again. Pins can only be sent to the emulator, not to a watch. See the [Pin Structure](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) guide for the format. ## Publishing The **Publish** pane submits a C or JavaScript SDK project to the Pebble appstore using the most recent successful build. It captures screenshots from the emulator or accepts uploads, and creates either a new listing or a new release of an existing app. Read [Publishing an App](https://developer.repebble.com/guides/appstore-publishing/publishing-an-app.md) for the details. ## Getting started 1. Go to [cloudpebble.repebble.com](https://cloudpebble.repebble.com) and sign in 2. Create a new project and choose your project type 3. Write your code and click the compile button to build 4. Test in the built-in emulator, or install on your watch via the [Pebble mobile app](https://repebble.com/app) ## Learn more - [Build a watchface with JavaScript (Alloy)](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/) - step-by-step tutorial - [Build a watchface with C](https://developer.repebble.com/tutorials/watchface-tutorial/) - step-by-step tutorial - [Example apps](https://developer.repebble.com/examples.md) - browse example projects - [Alloy guides](https://developer.repebble.com/guides/alloy.md) - in-depth guides for JavaScript development with Alloy, including Moddable example apps --- # Installing the Pebble SDK Source: https://developer.repebble.com/sdk/ The easiest way to start building a Pebble watchface or app is with [CloudPebble](https://cloudpebble.repebble.com) - no installation required. It runs entirely in your browser! Otherwise, if you are somewhat command-line knowledgeable, you can follow the instructions below to install the SDK locally on your computer. ## Install dependencies #### MacOS You will need to install Node.js and libpng. The easiest way is with [Homebrew](https://brew.sh/): ``` brew install node libpng ``` #### Ubuntu You will need to install a few dependencies to make the SDK run: ``` sudo apt install nodejs npm libsdl2-2.0-0 libglib2.0-0 libpixman-1-0 zlib1g libsndio7.0 ``` > Note: if you already have `node` installed, you can install only: ``` sudo apt install libsdl2-2.0-0 libglib2.0-0 libpixman-1-0 zlib1g libsndio7.0 ``` #### Fedora ``` sudo dnf install nodejs SDL2 glib2 pixman zlib sndio ``` #### Windows The Pebble SDK does not run on Windows, but you can use WSL. Install Ubuntu in WSL, and then use the instructions above to install dependencies. ## Download the Pebble CLI Install [uv](https://docs.astral.sh/uv/getting-started/installation/), a fast package manager for Python. Then, run: ``` uv tool install pebble-tool ``` > Note: The `pebble` tool requires Python 3.10 to 3.13. Python 3.14 is not supported yet. Not every language and feature is available on every watch. See the platform support table in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md). The `sdkVersion` field in `package.json` stays `"3"` for every app, including apps built with SDK 4.x. The version of the `pebble` tool (5.x) is not the version of the SDK (4.x); run `pebble sdk list` to see which SDKs are installed. ## Next Steps Now that you have the Pebble SDK downloaded and installed on your computer, it is time to create your first app! #### Create a Pebble app Install the latest SDK: ``` pebble sdk install latest ``` Create a project (for example, called myproject): ``` pebble new-project myproject ``` Compile the project (after `cd`ing to your project directory): ``` pebble build ``` Install the app on an emulator for the Pebble Time: ``` pebble install --emulator basalt ``` Or, install the app on the watch through the Pebble mobile app (install it from [repebble.com/app](https://repebble.com/app)). In the Pebble mobile app, sign in with your Pebble account, open the _Devices_ tab, tap the three dot icon on your watch and enable the _Dev Connection_ toggle. Then, on your computer, sign in to the same Pebble account and install: ``` pebble login pebble install --cloudpebble ``` See [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) for details and for the LAN connection, which works without signing in. #### Learn more The best way to learn is by checking out our examples apps: [weather](https://github.com/pebble-examples/pebblekit-js-weather/), [simple game](https://github.com/pebble-hacks/pandas-and-bananas/), [concentricity watchface](https://github.com/pebble-examples/concentricity/), and [many more](https://developer.repebble.com/examples.md)! Or try [tutorials](https://developer.repebble.com/tutorials.md) for a step-by-step guide on how to write a simple C Pebble application. #### Using an AI coding agent `pebble new-project --ai myproject` also writes instruction files for Claude Code and Cursor, and every page of this site has a Markdown version for agents to read. See [_Building with AI Agents_](https://developer.repebble.com/guides/ai-agents.md) for the files, the documentation index at `/llms.txt` and the emulator commands an agent can use to check its work. ### Installation Problems? On Linux, check the following first: - On WSL2, install `build-essential` before installing the SDK: `sudo apt install build-essential`. - The emulator needs glibc 2.38 or newer. Ubuntu 22.04 ships an older glibc, so use Ubuntu 24.04 or newer. - On Fedora, the emulator needs `libsndio`: `sudo dnf install sndio`. If the emulator gets into a strange state, for example it will not boot, keeps an app you have removed, or an install times out, run `pebble kill` to stop it and `pebble wipe` to reset its stored data, then install again. Check the [FAQ](https://developer.repebble.com/faqs.md) next - common install, emulator, and `pebble` tool errors are answered there. If you're still stuck, ask on the [Pebble Developer Forum](https://forum.repebble.com/c/developers-ask-questions-and-get-help/7). Please provide as many details as you can about the issues you may have encountered. **Tip:** Copying and pasting commands from your Terminal output will help a great deal. --- # Pebble SDK 4.33.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.33.1/ _Release Date: August 14 2026_ This is a hotfix for [SDK 4.33](https://developer.repebble.com/sdk/changelogs/4.33.md). Apps compiled with SDK 4.33.1 still install and run on firmware 4.33 and 4.33.1, but the recognizer crash fix below only ships on firmware 4.33.2; this hotfix only updates the emulator. ### Changes to Firmware - Fixed a crash when adding the new touch recognizers ([`app_touch_navigation_enable()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#app_touch_navigation_enable "app\_touch\_navigation\_enable")) to an app. ### Changes to SDK None. ### Changes to Emulator - The emulator now runs firmware 4.33.2, which includes the recognizer crash fix. --- # Pebble SDK 4.33 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.33/ _Release Date: August 12 2026_ This release rolls up everything that shipped in firmware 4.18 through 4.33. The headline features are touch support for apps, heart rate variability sampling, and a major Alloy (Moddable) update. ### Changes to Firmware - Touch navigation is now enabled by default on watches with a touchscreen. Taps and swipes are translated into button events for menus, scroll views, action bars and action menus. - Shake and tap events caused by the watch's own vibration are now filtered out, so [`accel_tap_service_subscribe()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_tap_service_subscribe "accel\_tap\_service\_subscribe") subscribers no longer receive spurious events during notifications. - Daily health metrics no longer saturate at 16-bit values, so step counts and calories above 65,535 are reported correctly by [`health_service_sum_today()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_today "health\_service\_sum\_today"). - Fixed the Back+Up button combination in left-handed mode. ### Changes to SDK - Added a touch gesture Recognizer API for apps: [`tap_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_create "tap\_recognizer\_create"), [`pan_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_create "pan\_recognizer\_create") and [`swipe_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_create "swipe\_recognizer\_create") create recognizers that report started/updated/completed/cancelled events to a callback, with helpers to query the tap point ([`tap_recognizer_get_tap_point()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_get_tap_point "tap\_recognizer\_get\_tap\_point")), pan deltas and velocity ([`pan_recognizer_get_total_delta()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_total_delta "pan\_recognizer\_get\_total\_delta"), [`pan_recognizer_get_delta_since_start()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_start "pan\_recognizer\_get\_delta\_since\_start"), [`pan_recognizer_get_delta_since_prev()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_prev "pan\_recognizer\_get\_delta\_since\_prev"), [`pan_recognizer_get_velocity()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_velocity "pan\_recognizer\_get\_velocity")) and swipe direction and velocity ([`swipe_recognizer_get_direction()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_direction "swipe\_recognizer\_get\_direction"), [`swipe_recognizer_get_velocity()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_velocity "swipe\_recognizer\_get\_velocity")). Recognizers are attached to a window with [`window_attach_recognizer()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_attach_recognizer "window\_attach\_recognizer"), and [`recognizer_set_simultaneous_with()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_simultaneous_with "recognizer\_set\_simultaneous\_with") / [`recognizer_set_fail_after()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_fail_after "recognizer\_set\_fail\_after") control how multiple recognizers interact. - Added [`app_touch_navigation_enable()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#app_touch_navigation_enable "app\_touch\_navigation\_enable") so apps can opt in to system touch navigation (existing apps are opted out by default), and [`window_set_touch_bridge_disabled()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_touch_bridge_disabled "window\_set\_touch\_bridge\_disabled") to keep the system from bridging touches to button events on a window that handles its own touch input. - [`TouchEvent`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent "TouchEvent") gained a `non_navigational` flag, set on touches that arrive while no interaction session is active. - Added heart rate variability APIs: the [`HealthEventHRVUpdate`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHRVUpdate "HealthEventHRVUpdate") event, `health_service_peek_hrv_ppi_ms()` to read the latest peak-to-peak interval, and `health_service_set_hrv_sample_period()` to request HRV sampling. HRV and heart rate sample periods are independent requests that share the app's sensor subscription. - Added [`alarm_service_peek_next()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md#alarm_service_peek_next "alarm\_service\_peek\_next") to query the next scheduled system alarm, e.g. for an alarm indicator on a watchface. - The maximum app binary size on Emery and Gabbro is now 128 KiB (up from 64 KiB). The loaded image and RAM footprint are still limited to 64 KiB each; the extra room is available for relocation data. - [`ACTION_BAR_WIDTH`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ACTION_BAR_WIDTH "ACTION\_BAR\_WIDTH") on Gabbro changed from 34 to 40 pixels. Apps that derive layout from this constant will adapt automatically when rebuilt. - [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") (and by extension [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer")) carries internal touch navigation state; `sizeof(ScrollLayer)` grows when recompiling apps that embed one by value. - The app loader now accepts binaries produced by newer ARM toolchains (unaligned relocation tables and targets) and rejects malformed binaries cleanly instead of corrupting memory. - The firmware C library backing the SDK's standard library functions ([`snprintf`](https://developer.repebble.com/docs/c/Standard_C/Format.md#snprintf "snprintf"), [`strftime`](https://developer.repebble.com/docs/c/Standard_C/Time.md#strftime "strftime"), etc.) is now picolibc. ### Changes to Alloy - Updated Moddable (Alloy) to SDK 8.3.1, bringing the XS engine to 17.8 with Explicit Resource Management: `using` declarations, `Symbol.dispose`, `DisposableStack` and `SuppressedError` are now available. Every closable Alloy class (buttons, sensors, messages, files, network clients and more) aliases `Symbol.dispose` to its `close()` method, so they can be managed with `using`. - Added the `pebble/health` module, a JavaScript interface to the Health service: read current metrics (`Health.metric.get()`), query sums and averages over time ranges (`Health.metric.query()`), check availability and permissions (`Health.metric.accessible()`), register metric threshold alerts (`Health.metric.Alert`), inspect activities (`Health.activity.get()` / `iterate()`), control the heart rate sample period (`Health.heartRate.samplePeriod`) and read minute-by-minute history (`Health.history.byMinute()`). - Added a `"health"` event on the `watch` global, delivering `"significant"`, `"movement"`, `"sleep"`, `"metric"`, `"heart rate"` and `"heart rate variability"` updates. - `pebble/button` now supports click recognizers: configure `single` (with an optional repeat interval), `long` (with an optional delay), `multi` (with click count and timeout options) or `raw` handling per button, and the `onPush` callback now also reports which recognizer fired, the click count and whether it is a repeat. Existing code using the previous press/release behavior continues to work unchanged (`raw` is the default). - `trace()` output now goes to the app log when the xsbug debugger is not connected, so it shows up in `pebble logs`. - Fixed a crash when timers or app messages fired while an app was exiting. - Compatibility note: apps built with this SDK use XS 17.8 byte code and require firmware 4.32 or later. Apps built with older SDKs (4.17 or lower) continue to install and run on new firmware without a rebuild. ### Changes to Emulator - The emulator now supports injecting health metrics (steps, distance, calories, active time, sleep). --- # Pebble SDK 4.17 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.17/ _Release Date: June 23 2026_ Welcome to another SDK release! We've taken quite some time on this one we hope you'll enjoy it! ### Changes to SDK - Added [`persist_get_max_size()`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_get_max_size "persist\_get\_max\_size") to query the per-app persistent storage limit at runtime. - Added [`speaker_is_muted()`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_is_muted "speaker\_is\_muted") to check the system-wide speaker mute state. - Added a backlight service for apps: [`backlight_service_subscribe()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#backlight_service_subscribe "backlight\_service\_subscribe") / [`backlight_service_unsubscribe()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#backlight_service_unsubscribe "backlight\_service\_unsubscribe") with a [`BacklightHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#BacklightHandler "BacklightHandler") callback that fires when the backlight turns on or off. - Added [`launch_button()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_button "launch\_button") and [`launch_get_quick_launch_action()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_get_quick_launch_action "launch\_get\_quick\_launch\_action") (with the new [`AppQuickLaunchAction`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppQuickLaunchAction "AppQuickLaunchAction") type) so apps can tell how they were quick launched. - Exposed the speaker playback limits [`SPEAKER_MAX_NOTES`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SPEAKER_MAX_NOTES "SPEAKER\_MAX\_NOTES") (256), [`SPEAKER_MAX_TRACKS`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SPEAKER_MAX_TRACKS "SPEAKER\_MAX\_TRACKS") (4), and [`SPEAKER_MAX_SAMPLE_BYTES_TOTAL`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SPEAKER_MAX_SAMPLE_BYTES_TOTAL "SPEAKER\_MAX\_SAMPLE\_BYTES\_TOTAL") (16 KiB) so apps can validate input before calling the Speaker API. - Added the [`kModdableCreationFlagDebug`](https://developer.repebble.com/docs/c/Foundation/Alloy.md#kModdableCreationFlagDebug "kModdableCreationFlagDebug") flag for Alloy (Moddable) apps to enable xsbug debugging. - Updated Moddable (Alloy) to SDK 8.2.3. - Added FFI (Foreign Function Interface) support to Alloy, letting JavaScript apps call native C functions directly. - Expanded the Alloy JavaScript API with new modules and globals: - `pebble/dictation` - capture speech as text. - `pebble/wakeup` - schedule the app to launch at a future time, with a `"wakeup"` event and `watch.wake` launch info. - `pebble/vibes` - short/long/double pulses and custom vibration patterns. - `watch.light()` - control the backlight. - `watch` / `screen` / `device` info (`watch.model`, `watch.firmwareVersion`, `watch.hour12`, `watch.launch`, `screen.width` / `height` / `round` / `color`, `device.sensor`). - App lifecycle and display events on `watch`: `"willFocus"`, `"didFocus"`, and `"resize"`. - Added a `pebble build --debug` mode that compiles your app without optimizations (`-O0`) and defines `PBL_DEBUG` for easier debugging. For Alloy (Moddable) apps it also enables the xsbug JavaScript debugger. Debug builds are written to a `_debug.pbw` bundle. - `pebble install` now auto-launches xsbug and bridges the JavaScript debugger over the connection when installing a `--debug` Alloy build, so the debugger is attached before the app boots. (`pebble logs` likewise prefers the `_debug` bundle when present.) - Requires `pebble-tool` ≥5.0.38. --- # Pebble SDK 4.9.169 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.9.169/ _Release Date: May 1 2026_ ### Changes to Firmware - Added speaker playback support, including an app-facing Speaker API. - Added RGB backlight support with a new backlight color preference and modal preempt behavior when the color changes. - Added a Touch toggle in Settings and a global touch enable flag; touch events trigger the backlight while a touch app is subscribed. - Added a Health Tracking on/off toggle in Settings, plus a setting to toggle heart-rate tracking during activities. HRM activity tracking is now off by default. - Added music app settings for volume controls and a progress bar. - Emery layout improvements: adapted alarm and calendar timeline layouts, adjusted kickstart and watchface offsets, used larger LECO font on the low-power watchface, and used larger workout fonts on big displays. - Added QEMU virtual platforms and drivers for `qemu_emery`, `qemu_flint`, and `qemu_gabbro` (including RGB backlight, Audio, and a QEMU touch driver). - Various bug fixes and stability improvements. ### Changes to SDK - Added Speaker API for apps, gated by the new `PBL_SPEAKER` define (set on Emery and Flint). - Exposed the touch service API to apps, with a new `PBL_TOUCH` define on Emery and Gabbro and an `is_enabled` query plus per-task state. - Added [`light_set_color`](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_color "light\_set\_color") / [`light_set_system_color`](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_system_color "light\_set\_system\_color") and the new `PBL_RGB_BACKLIGHT` define on Emery for full-tint backlight control; [`light_set_color_rgb888()`](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_color_rgb888 "light\_set\_color\_rgb888") is also available. - Added [`rot_bitmap_layer_get_layer()`](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_get_layer "rot\_bitmap\_layer\_get\_layer") and the [`AppGlanceSliceLayout`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSliceLayout "AppGlanceSliceLayout") type to the public API. - Updated Moddable (Alloy) to SDK 8.0.0, adding Pebble Touch support. - Built `.pbw` files now have private metadata stripped. - Fixed the aplite check in [`PBL_PLATFORM_SWITCH_DEFAULT`](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_SWITCH_DEFAULT "PBL\_PLATFORM\_SWITCH\_DEFAULT"). - Requires `pebble-tool` ≥5.0.32. --- # Pebble SDK 4.9.148 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.9.148/ _Release Date: March 26 2026_ **Important:** Due to an oversight in SDK 4.9.127, all Alloy apps compiled with that version need to be recompiled with SDK 4.9.148. We apologize for the inconvenience. ### Changes to Firmware - Added Hebrew language support, including Heebo fonts and improved right-to-left / bidirectional text rendering. - Added support for Unicode space characters (em space, en space, non-breaking space, etc.) with correct widths and line-breaking behavior. - Added manual time and date setting in the Settings menu, with a toggle between automatic (phone sync) and manual time sources. - Added Back + Up button combo as a new quick launch shortcut from the watchface. - Added deep sleep tracking display on black-and-white watches. - Added heart rate monitoring interval setting. - Improved display rendering: fixed bitblt overflow crash on displays wider than 255 pixels, forced display refresh on orientation changes, and fixed reversed bezel-mode animation direction. - Removed RockyJS / JerryScript runtime support. JavaScript apps should use the Alloy (Moddable) framework introduced in SDK 4.9.127. - Various bug fixes and stability improvements. ### Changes to SDK - Added `RESOURCE_ID_LECO_60_NUMBERS_AM_PM` and `RESOURCE_ID_LECO_60_BOLD_NUMBERS_AM_PM` system font resources. - Added [`kModdableCreationFlagLogInstrumentation`](https://developer.repebble.com/docs/c/Foundation/Alloy.md#kModdableCreationFlagLogInstrumentation "kModdableCreationFlagLogInstrumentation") flag for Alloy apps to enable XS instrumentation logging. Only really needed for advanced use-cases! - Updated Moddable (XS engine) to version 7.2.1. --- # Pebble SDK 4.9.127 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.9.127/ _Release Date: February 20 2026_ After some test releases and a lot of hard work, we're excited to announce the release of Pebble SDK 4.9.127! This release includes a number of new features and improvements, such as the new Alloy JavaScript framework, support for Pebble Round 2, and much more. We also made various bug fixes and stability improvements to ensure a better development experience. ### Changes to Firmware - Added QEMU virtual platforms for development: `silk_flint`, `snowy_emery`, and `spalding_gabbro`. - Added legacy app rendering mode, allowing apps compiled for older platforms to run scaled on larger displays. - Added Arabic text shaping and right-to-left (RTL) text support, including mixed RTL-LTR text with correct justification. - Added SpO2 support in the HRM manager. - Added accent/highlight color settings with contrast-compatible colors. - Added display orientation / left-handed mode support. - Added QR code rendering support. - Notification improvements: custom vibration patterns, timed mute options, alternative notification designs, and expanded known app icon mappings. - Various bug fixes and stability improvements! ### Changes to SDK - Added Alloy, a modern JavaScript framework based on the Moddable SDK, for building Pebble apps with support for the latest features and platforms. - Ported all SDK scripts and tools to Python 3. - Updated WAF build system to 2.1.4. - Updated Toolchain to GCC 14 (note that this may cause some warnings in older code). - Added `ctx.pbl_suppress_newer_gcc_warnings()` to suppress warnings in legacy code when using the new toolchain. (Add to configure in your wscript) - Added BDF and PBF font support for resources. - Added `moddable` project type to create apps using the Alloy framework. - Added `Flint` and `Gabbro` platforms. - Increased `MAX_FONT_GLYPH_SIZE` to 512 for emery and gabbro. - Added 1% battery reporting granularity on new QEMU platforms. - Requires `pebble-tool` ≥5.0.23. --- # Pebble SDK 4.3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.3/ _Release Date: November 23 2016_ ### Changes to Firmware - Added heart rate zones to the Health app. - Added a heart rate measurement characteristic to the Pebble GATT profile, enabling mobile apps to retrieve live heart rate data from Pebble watches with a built-in heart rate monitor. - Added [`HealthMetricHeartRateRawBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateRawBPM "HealthMetricHeartRateRawBPM") to allow access to raw heart rate measurements from supported watches. - Added Kickstart watchface. - Added the display of heart rate measurements in the sports app when a 3rd party app uses the Sports API. - Added voice reminders on Pebble for iOS users. - Improved measurements of heart rate on Pebble 2 devices. - Bug fixes and improvements. ### Changes to SDK - Fixed a bug where the Rocky.js `minutechange` event was not emitted when the hour or day changed. - Added [`quiet_time_is_active`](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#quiet_time_is_active "quiet\_time\_is\_active") to permit C apps to check if Quiet Time is active on a watch. - Added the alias `app_package.json` to the webpack.config.js file, to allow Pebble Packages to `require('app_package.json')`, which will require an app's package.json or appinfo.json file during JS bundling. - Added support for specifying custom PebbleKit JS and Rocky.js entry points from within a Rocky.js project's package.json file. ### Changes to PebbleKit Android (v4.0.1) - Added new APIs for sending custom HR data and a custom label/value from a mobile companion app, using the Sports API. - Added a helper object that will automatically manage state and message construction for updates to the Sports app on a Pebble watch. - Updated the Constant `ICON_MAX_DIMENSIONS` to be 25px, to be consistent with watches running 4.0 firmware. If your provided icon exceeds 25px x 25px, you will encounter a runtime error when calling `customizeWatchApp`. ### Changes to PebbleKit iOS (v4.0.0) - Added support for Diorite and Emery watches. - Added new APIs for sending custom HR data and a custom label/value from a mobile app. - Added a helper object that will automatically manage state and message construction for updates to the Sports app on a Pebble watch. - Removed Bluetooth Classic support. Mobile apps compiled with this version of PebbleKit will only communicate with watches running FW 3.8 and later, which use BLE for communications. Mobile apps using PebbleKit iOS 4.0.0 and later will no longer need to be whitelisted for use with Pebble. --- # Pebble SDK 4.2.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.2.2/ _Release Date: November 2 2016_ This is a hotfix for [SDK 4.2](https://developer.repebble.com/sdk/changelogs/4.2.md). No corresponding firmware exists. ### Changes to Firmware - The glyph size limit for Emery has been increased. It will be increased for Basalt, Chalk, and Diorite in a future update. ### Changes to SDK - App builds now generate a sourcemap for PebbleKit JS code, which the pebble tool can use to resolve line numbers in debug output. --- # Pebble SDK 4.2.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.2.1/ _Release Date: October 19 2016_ This is a hotfix for [SDK 4.2](https://developer.repebble.com/sdk/changelogs/4.2.md). No corresponding firmware exists. ### Changes to Firmware None. ### Changes to SDK - Removed the use of `eval` in JavaScript bundling that was causing incorrect behavior when running in the emulator and on iOS. ### Known issues - JavaScript errors do not have a useful filename or line number. You may find it helpful to look up the line in build/pebble-js-app.js to determine what the error is referring to. --- # Pebble SDK 4.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.2/ _Release Date: October 18 2016_ ### Changes to Firmware - Emery can now run apps compiled for Basalt (SDK 4.1 or earlier) or Aplite (SDK 3.7 or earlier) in "bezel mode". - Fixed [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") for heartrate-related metrics. - Note that, as heartrate data is only retained for two hours, HRM accessibility checks going back further than two hours will always return `false`. - Rocky.js: The reliability of the `postMessage` API has been improved. - Rocky.js: `postmessageconnected`, `postmessagedisconnected` and `postmessageerror` events have been added. - Rocky.js: Using regular expressions no longer results in substantial app log spam. - Rocky.js: The coordinate system was adjusted by 0.5 pixels; (0, 0) now refers to top left of the first pixel, rather than the center of the pixel. - Rocky.js: The `memorypressure` event has been added. When fired with `{level: 'high'}`, the app must free up sufficient memory or it will be terminated. - Rocky.js: Content size is exposed via `UserPreferences`. - Rocky.js: `watchInfo.platform` now works as expected in the emulator. - Rocky.js: Removed the global `print()` function; use `console.log()`. - Rocky.js: Fixed a crash when passing invalid values to `clearTimeout` or `clearInterval`. - Rocky.js: Provided constructors for `CanvasRenderingContext2D`, `RockyCanvasElement` and `Event`. - Rocky.js: Removed unimplemented methods that previously threw an exception when called, allowing for polyfills of those methods. - Rocky.js: Added the ability to unsubscribe from events using [`removeEventListener`](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#removeEventListener "removeEventListener") or `off`. - Bug fixes and improvements. ### Changes to SDK - Added support for the Emery platform. - Rocky.js is now considered stable, and can be used to submit apps to the appstore. - Rocky.js: The default app template has been improved. - Added [`preferred_content_size()`](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size "preferred\_content\_size") to retrieve the user's preferred font size. - All JS bundling is now performed using [webpack](https://webpack.github.io) - Only JavaScript files that are actually used are now bundled. --- # Pebble SDK 4.2-beta5 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.2-beta5/ _Release Date: October 14 2016_ This is a developer preview for SDK 4.2. No firmware is provided. ### Changes to Firmware - Rocky.js: assorted postMessage fixes. ### Changes to SDK - Fixed apps that use an external bundler and expect `require` to exist at runtime. - This also fixes Clay. --- # Pebble SDK 4.2-beta4 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.2-beta4/ _Release Date: October 12 2016_ This is a developer preview for SDK 4.2. No firmware is provided. ### Changes to Firmware - Emery can now run apps compiled for Basalt (SDK 4.1 or earlier) or Aplite (SDK 3.7 or earlier) in "bezel mode". - Fixed [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") for heartrate-related metrics. - Rocky.js: The reliability of the `postMessage` API has been improved. - Rocky.js: `postmessageconnected`, `postmessagedisconnected` and `postmessageerror` events have been added. - Rocky.js: Using regular expressions no longer results in substantial app log spam. - Rocky.js: The default app template has been improved. - Rocky.js: The coordinate system was adjusted by 0.5 pixels; (0, 0) now refers to top left of the first pixel, rather than the center of the pixel. - Rocky.js: The `memorypressure` event has been added. When fired with `{level: 'high'}`, the app must free up sufficient memory or it will be terminated. - Rocky.js: Content size is exposed via `UserPreferences`. - Rocky.js: `watchInfo.`platform now works as expected in the emulator. - Rocky.js: Removed the global `print()` function; use `console.log()`. - Rocky.js: Fixed a crash when passing invalid values to `clearTimeout` or `clearInterval`. - Rocky.js: Provided constructors for `CanvasRenderingContext2D`, `RockyCanvasElement` and `Event`. - Rocky.js: Removed unimplemented methods that previously threw an exception when called, allowing for polyfills of those methods. - Rocky.js: Added the ability to unsubscribe from events using [`removeEventListener`](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#removeEventListener "removeEventListener") or `off`. - Bug fixes and improvements. ### Changes to SDK - Added support for the Emery platform. - Rocky.js is now considered stable, and can be used to submit apps to the appstore. - Added [`preferred_content_size()`](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size "preferred\_content\_size") to retrieve the user's preferred font size. - All JS bundling is now performed using [webpack](https://webpack.github.io) - Only JavaScript files that are actually used are now bundled. ### Known Issues - Clay does not work after building with SDK 4.2-beta4. - Apps that use an external bundler and therefore expect `require` to exist at runtime in order to `require('message_keys')` will not work. --- # Pebble SDK 4.1.4 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.1.4/ _Release Date: October 5 2016_ This is a hotfix for [SDK 4.1](https://developer.repebble.com/sdk/changelogs/4.1.md). No corresponding firmware exists. ### Changes to Firmware None. ### Changes to SDK - Fixed a build failure when using only tagged icons. - Fixed a failure to validate the correct icon or published resource when using tagged resources. (This is the same as 4.1.1, but the fixes were inadvertently reverted in 4.1.2.) ### Known issues - [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") always returns `false` when checking the accessibility of [`HealthMetricHeartRateBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateBPM "HealthMetricHeartRateBPM"). Instead, use `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` --- # Pebble SDK 4.1.2 and 4.1.3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.1.2/ _Release Date: October 4 2016_ 4.1.2 is a hotfix for [firmware 4.1](https://developer.repebble.com/sdk/changelogs/4.1.md). 4.1.3 removes some debug apps that were inadvertently included in 4.1.2. ### Changes to Firmware - Fixed an issue where the Rocky.js `Date` constructor did not work for dates in October on a leap year. This also fixes an issue where Tic-Toc would fail after one minute. ### Changes to SDK None. ### Known issues - [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") always returns `false` when checking the accessibility of [`HealthMetricHeartRateBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateBPM "HealthMetricHeartRateBPM"). Instead, use `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` --- # Pebble SDK 4.1.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.1.1/ _Release Date: September 29 2016_ This is a hotfix for [SDK 4.1](https://developer.repebble.com/sdk/changelogs/4.1.md). No corresponding firmware exists. ### Changes to Firmware None. ### Changes to SDK - Fixed a build failure when using only tagged icons. - Fixed a failure to validate the correct icon or published resource when using tagged resources. ### Known issues - [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") always returns `false` when checking the accessibility of [`HealthMetricHeartRateBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateBPM "HealthMetricHeartRateBPM"). Instead, use `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` --- # Pebble SDK 4.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.1/ _Release Date: September 27 2016_ ### Changes to Firmware - Added support for Pebble 2 and the Diorite platform. - New and improved launcher animations for rectangular watches. - Fully implemented heartrate APIs. - Fixed a crash when using the compass in the emulator. - Fixed a _different_ crash when using the compass on a real watch. - Fixed a crash when using templated AppGlance strings on Pebble 2 watches. - Fixed Rocky apps crashing on Chalk. - Improved launcher performance. - Updated the timezone database. ### Changes to SDK - Added some missing heartrate-related aplite compatibility macros. - Added Pebble 2 color codes to [`WatchInfoModel`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoModel "WatchInfoModel"). - Timeline past is now accessible in the SDK shell using the up button. - Oversized menu icons will now fail at build time instead of runtime. - `publishedMedia` can now reference resources inside Pebble Packages. ### Known issues - If you use platform tags on your menu icon (e.g. `icon~bw.png` and `icon~color.png`, but no `icon.png`), your build will fail. You can work around this by creating an unused, un-tagged icon. This file will be validated, so it must be a valid icon. - [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") always returns `false` when checking the accessibility of [`HealthMetricHeartRateBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateBPM "HealthMetricHeartRateBPM"). Instead, use `health_service_metric_aggregate_averaged_accessible(HealthMetricHeartRateBPM, time_start, time_end, HealthAggregationAvg, HealthServiceTimeScopeOnce)` --- # Pebble SDK 4.0.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.0.1/ _Release Date: September 6 2016_ This is a hotfix for [SDK 4.0](https://developer.repebble.com/sdk/changelogs/4.0.md). The #pebblepast will be televized. ### Changes to Firmware - Timeline Past can now be assigned to a Quick Launch button. - Fixed an issue causing the Tylt Vü to not function. - Fixed a number of issues with AppGlance icons. ### Changes to SDK - Restored the functionality of `pebble gdb` (requires pebble tool 4.4.1) ### Known issues - Timeline Past is still not available in the emulator; it will be restored in a later update. --- # Pebble SDK 4.0 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.0/ _Release Date: August 30 2016_ ### Changes to Firmware - Added support for [Pebble 2](https://www.kickstarter.com/projects/597507018/pebble-2-time-2-and-core-an-entirely-new-3g-ultra). - Added [AppGlances](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) and [Timeline Quick View](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md). - Removed Timeline Past. ### Changes to SDK - Added the new "Diorite" platform for the Pebble 2. - Added support for writing watchfaces in JavaScript running directly on the watch using [Rocky.js](https://developer.repebble.com/blog/2016/08/15/introducing-rockyjs-watchfaces.md). - Added `PBL_COMPASS`, `PBL_SMARTSTRAP_POWER`, and `PBL_PLATFORM_DIORITE` defines. - Added [`preferred_result_display_duration`](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_result_display_duration "preferred\_result\_display\_duration") to get the recommended number of milliseconds a result window should be visible before it should be closed. - Added [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") and [`exit_reason_set`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#exit_reason_set "exit\_reason\_set") for an application to be able to notify the system of [the reason it is exiting](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md). - Added [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice"), [`AppGlanceResult`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceResult "AppGlanceResult"), [`AppGlanceReloadSession`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadSession "AppGlanceReloadSession"), [`app_glance_add_slice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice "app\_glance\_add\_slice"), [`AppGlanceReloadCallback`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback "AppGlanceReloadCallback") and [`app_glance_reload`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload"). to support [AppGlances](https://developer.repebble.com/guides/user-interfaces/appglance-c.md). - Added [Unobstructed Area APIs](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md): [`UnobstructedAreaWillChangeHandler`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaWillChangeHandler "UnobstructedAreaWillChangeHandler"), [`UnobstructedAreaChangeHandler`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaChangeHandler "UnobstructedAreaChangeHandler"), [`UnobstructedAreaDidChangeHandler`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaDidChangeHandler "UnobstructedAreaDidChangeHandler"), [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers"), [`layer_get_unobstructed_bounds`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds "layer\_get\_unobstructed\_bounds"), [`unobstructed_area_service_subscribe`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_subscribe "unobstructed\_area\_service\_subscribe") and [`unobstructed_area_service_unsubscribe`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_unsubscribe "unobstructed\_area\_service\_unsubscribe") to enable a watchface to adapt to overlays partially obstructing it, such as during a Timeline Quick View. - The compass service will now return [`CompassStatusUnavailable`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusUnavailable "CompassStatusUnavailable") on diorite watches, which do not have a compass. Apps built with SDK 2 or SDK 3 will still see [`CompassStatusDataInvalid`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusDataInvalid "CompassStatusDataInvalid"). - Report memory usage for Pebble Packages at build time. - Fixed bug causing zero-length JS files to result in build failures. ### Changes to Documentation - Added [AppGlances Guide](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) - Added [Unobstructed Area Guide](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md) - Added [AppExitReason Guide](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md) - Added [One Click Action Guide](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) - Added API documentation for new [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance"), [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") and [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") APIs. ### Known Issues - `pebble gdb` is currently broken. - AppGlances cannot currently use system icons. - Custom AppGlance icons may not work if the assigned published media ID does not happen to also be a standard resource ID. This can usually be worked around by assigning published media IDs starting at 1 and counting up. - For apps not using AppGlances, the system default icon may appear instead of the app-specified menu icon if the resources defined for the app vary between platforms. This can be worked around by defining the menu icon resource before defining any resources that use the `targetPlatforms` attribute. --- # Pebble SDK 4.0-rc20 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.0-rc20/ _Release Date: August 29 2016_ This is a developer release candidate for SDK 4.0. No firmware is provided. ### Changes to Firmware - Apps now correctly launch when installed in the SDK shell. - Rocky bugfixes. - Other bugfixes. ### Changes to SDK - This SDK will not install in any pebble tool prior to 4.4-rc1. - Corrected outdated AppGlance-related definitions in the aplite compatibility header. ### Known Issues - Using system icons for appglances is currently broken. Using custom icons for them, however, works fine. - `pebble gdb` is currently broken. --- # Pebble SDK 4.0-beta17 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.0-beta17/ _Release Date: August 16 2016_ This is a developer preview release of SDK 4.0. No firmware is provided. ### Changes to Firmware - Added support for [running developer apps written in JavaScript](https://developer.repebble.com/blog/2016/08/15/introducing-rockyjs-watchfaces.md) ### Changes to SDK - AppGlance icons must now be provided as references to `publishedMedia` in package.json. Providing plain resource IDs will not work. - Custom timeline icons can now be created via `publishedMedia`. - Added support initial for building JavaScript apps. These apps won't support aplite. ### Known Issues - `publishedMedia` is completely undocumented. Updated documentation will be available soon. In the interim, continue using 4.0-dp3 to develop appglances. --- # Pebble SDK 4.0-dp3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.0-dp3/ _Release Date: July 6 2016_ This is a developer preview release of SDK 4.0. No firmware is provided. ### Changes to Firmware - Fixed diorite vibrating on boot until cancelled. - App icons can now be provided in PDC format. - Changed the watchfaces icon! ### Changes to SDK - Renamed AppGlanceSlice.layout's `template_string` to `subtitle_template_string` and `icon_resource_id` to `icon`. You will need to update your apps appropriately for them to compile. - The compass service will now return [`CompassStatusUnavailable`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusUnavailable "CompassStatusUnavailable") on diorite watches, which do not have a compass. Apps built with SDK 2 or SDK 3 will still see [`CompassStatusDataInvalid`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusDataInvalid "CompassStatusDataInvalid"). - Added a `PBL_API_EXISTS` macro, for checking if an API is available in a the current SDK at compile time. This macro was also added to SDK 3.14. - Fixed a bug that caused messageKeys containing an underscore to be mangled. ### Known Issues - The `UnobstructedAreaHandler` object must be created before being passed into the `unobstructed_area_service_subscribe` method. --- # Pebble SDK 3.14 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.14/ _Release Date: July 6 2016_ This firmware was released only for the Pebble Time series. ### Changes to Firmware - Adjusted the Health alerts to be more positive and less condescending. - "Average" is now "Typical", to match the phone apps. - Improved the smart alarm algorithm. - Improved syncing of health data to the phone. - Fixed a potential crash loop when receiving unexpected ANCS data on iOS. ### Changes to SDK - Added `PBL_API_EXISTS` macro, to test whether a function exists in a given SDK at compile time. This macro will also be added to the 4.0-dp3 preview. - Fixed a bug that caused messageKeys containing an underscore to be mangled. --- # Pebble SDK 4.0-dp2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.0-dp2/ _Release Date: June 22 2016_ This is a developer preview release of SDK 4.0. No firmware is provided. ### Changes to Firmware - Fixed a bug causing [`APP_GLANCE_SLICE_NO_EXPIRATION`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_NO_EXPIRATION "APP\_GLANCE\_SLICE\_NO\_EXPIRATION") to mean "expire immediately" rather than the intended "expire never". - Fixed a bug causing empty app glances (i.e. attempts to remove all glances) to be ignored. ### Changes to SDK - None. ### Known Issues - The `UnobstructedAreaHandler` object must be created before being passed into the `unobstructed_area_service_subscribe` method. - The Diorite emulator is still a little shy and will occassionally shake upon starting. If your emulator doesn't stop shaking, try installing an app - that usually brings it out of its shell. --- # Pebble SDK 4.0-dp1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/4.0-dp1/ _Release Date: June 15 2016_ This is the first Developer Preview release of the brand new Pebble SDK 4.0. This changelog collects only changes visible to developers using the emulator. A complete changelog will be provided when 4.0 ships on physical watches. ### Changes to Firmware - Added support for [Pebble 2](https://www.kickstarter.com/projects/597507018/pebble-2-time-2-and-core-an-entirely-new-3g-ultra). - Added [AppGlances](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) and Timeline ~~Peek~~ Quick View. ### Changes to SDK - Added the new "Diorite" platform for the Pebble 2. - Added `PBL_COMPASS`, `PBL_SMARTSTRAP_POWER`, and `PBL_PLATFORM_DIORITE` defines. - Added [`preferred_result_display_duration`](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_result_display_duration "preferred\_result\_display\_duration") to get the recommended number of milliseconds a result window should be visible before it should closed. - Added [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") and [`exit_reason_set`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#exit_reason_set "exit\_reason\_set") for an application to be able to notify the system of [the reason it is exiting](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md). - Added [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice"), [`AppGlanceResult`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceResult "AppGlanceResult"), [`AppGlanceReloadSession`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadSession "AppGlanceReloadSession"), [`app_glance_add_slice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice "app\_glance\_add\_slice"), [`AppGlanceReloadCallback`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback "AppGlanceReloadCallback") and [`app_glance_reload`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload"). to support [AppGlances](https://developer.repebble.com/guides/user-interfaces/appglance-c.md). - Added [Unobstructed Area APIs](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md): [`UnobstructedAreaWillChangeHandler`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaWillChangeHandler "UnobstructedAreaWillChangeHandler"), [`UnobstructedAreaChangeHandler`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaChangeHandler "UnobstructedAreaChangeHandler"), [`UnobstructedAreaDidChangeHandler`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaDidChangeHandler "UnobstructedAreaDidChangeHandler"), [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers"), [`layer_get_unobstructed_bounds`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds "layer\_get\_unobstructed\_bounds"), [`unobstructed_area_service_subscribe`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_subscribe "unobstructed\_area\_service\_subscribe") and [`unobstructed_area_service_unsubscribe`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_unsubscribe "unobstructed\_area\_service\_unsubscribe") to enable a watchface to adapt to overlays partially obstructing it, such as during a Timeline ~~Peek~~ Quick View. - Added [`HealthMetricAlert`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert "HealthMetricAlert"), [`HealthAggregation`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation "HealthAggregation"), [`health_service_peek_current_value`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_peek_current_value "health\_service\_peek\_current\_value"), [`health_service_peek_current_value`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_peek_current_value "health\_service\_peek\_current\_value"), [`health_service_aggregate_averaged`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged "health\_service\_aggregate\_averaged"), [`health_service_aggregate_averaged`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged "health\_service\_aggregate\_averaged"), [`health_service_metric_aggregate_averaged_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_aggregate_averaged_accessible "health\_service\_metric\_aggregate\_averaged\_accessible"), [`health_service_register_metric_alert`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert "health\_service\_register\_metric\_alert"), [`health_service_register_metric_alert`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert "health\_service\_register\_metric\_alert") and [`health_service_cancel_metric_alert`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_cancel_metric_alert "health\_service\_cancel\_metric\_alert") in preparation for heart rate support. Note that these are not yet implemented. - Report memory usage for Pebble Packages at build time. ### Changes to Documentation - Added [AppGlances Guide](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) - Added [Unobstructed Area Guide](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md) - Added [AppExitReason Guide](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md) - Added [One Click Action Guide](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) - Added API documentation for new [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService"), [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance"), [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") and [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") APIs. ### Known Issues - Creating an [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") with a .expiration\_time of `APP_GLANCE_SLICE_NO_EXPIRATION_TIME` results in an AppGlanceSlice that is never displayed. - The [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers") object must be created before being passed into the [`unobstructed_area_service_subscribe`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_subscribe "unobstructed\_area\_service\_subscribe") method. - The Diorite emulator is still a little shy and will occassionally shake upon starting. If your emulator doesn't stop shaking, try installing an app - that usually brings it out of its shell. --- # Pebble SDK 3.13 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.13.1/ _Release Date: June 10 2016_ This is a hotfix for [SDK 3.13](https://developer.repebble.com/sdk/changelogs/3.13.md). This is an SDK-only release; no corresponding firmware exists. ### Changes to SDK - Use the value of package.json's `pebble.displayName` for the app's name everywhere, instead of sometimes using `name`, which is subject to npm naming restrictions. - When using a block AppMessage key (e.g. `Elements[6]`), the name of the base key is no longer usable directly and must be accessed numerically. This change was made to ensure that it _could_ be accessed numerically, which is the standard use-case for these array-like keys. - Library capabilities (e.g. `location`) are now correctly merged with app capabilities. --- # Pebble SDK 3.13 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.13/ _Release Date: June 7 2016_ This firmware was released only for the Pebble Time series. ### Changes to Firmware - Added the Weather app (requires mobile app 3.13). - Reduced confusion between walking and sleeping in Pebble Health. - Added U+1F525 FIRE (🔥) to the supported emoji. - `layer_set_value()` functions now always mark the layer as dirty. - Removed complaints about sleeping less than usual if you none-the-less slept plenty. - Improved Pebble Health summary notifications - [`gcolor_equal()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gcolor_equal "gcolor\_equal") now considers all transparent functions to be equivalent. This change only affects apps built with SDK 3.13 or later. ### Changes to SDK - [We launched Pebble Packages](https://developer.repebble.com/blog/2016/06/07/pebble-packages.md), our new, npm-based package solution! - Check out our [Pebble Package guides](https://developer.repebble.com/guides/pebble-packages.md)! - **appinfo.json** is deprecated; long live **package.json**! Use `pebble convert-project` to update your app. - We now include Pebble Packages that you have installed in your `node_modules` folder for use in the C or PebbleKit JS code of your app. - We also include standard node modules for use in PebbleKit JS (but compatibility varies). - We can now create packages as a new project type. Try `pebble new-package`! - Packages can also use [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") and resources without identifier conflicts. - `require` in PebbleKit JS now requires explicitly using relative paths when loading files in your app (i.e. prefixing filenames with `./`). - [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") keys are now included in your C code with a `MESSAGE_KEY_` prefix. They are also included in your PebbleKit JS code as the return value of `require("message_keys")`. - [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") keys can be auto-assigned, so numbers don't have to be picked explicitly for communication between C and PebbleKit JS. This is designed to enable libraries to function without conflicts. --- # Pebble SDK 3.12 and 3.12.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.12/ _Release Date: May 10 2016_ ### Changes to Firmware - Added Smart Alarms, which are smarter than regular alarms because they try to avoid waking you from a deep sleep. - Added support for the Send Text app on iOS with Time-series watches and supported carriers. - iOS users with Time-series watches and supported carriers can now respond to phone calls with text messages. - Added Pebble Health notifications for runs and long walks, and daily activity and sleep. - Added notification icons for Amazon, LinkedIn, Slack, Google Maps, Google Photos and the iOS Photos app (only on Time-series watches). - Restored the ability to control general vibration strength (lost in 3.11 on Time-series watches). - Removed weekly activity and sleep charts from the health app (they're in the phone app now). - The default watchface, TicToc, is now written in and running as JavaScript on Pebble Time and Pebble Time Steel. - [`health_service_get_minute_history`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_minute_history "health\_service\_get\_minute\_history") now correctly rounds the start time down to the nearest minute. - Fixed an issue where snooze on reminders sometimes never reminded you again. - Fixed popups still showing after entering low-power mode. - Fixed a crash when a notification is dismissed on the phone while performing a voice reply to that notification. - Fixed the watch briefly freezing after backing out of a progress window. - Fixed some incorrect timezone data. - Improved rendering of the sleep ring in the Health app after more than twelve hours of sleep. - Improved rendering of adjacent deep sleep sessions in the Health app. ### Changes to SDK - Each line of build output now indicates which platform it applies to. ### Changes to Documentation - Improved description of [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer")-related constants. --- # Pebble SDK 3.11.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.11.1/ _Release Date: April 4 2016_ This is a hotfix for [SDK 3.11](https://developer.repebble.com/sdk/changelogs/3.11.md) ### Changes to Firmware - Removed a non-functional app called `JavascriptTest` from the launcher menu. ### Changes to SDK None. --- # Pebble SDK 3.11 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.11/ _Release Date: April 4 2016_ ### Changes to Firmware - Added a vibrations settings menu with new custom vibration patterns. - Added sleep classification for naps and a pin to the timeline when a nap is completed. - Changed the cutoff time for completed sleep sessions to be 9pm so sleep sessions ending after 9pm are recorded on the following day. - Sleep summary screens are now hidden if no sleep data is collected overnight, and sleep graphs are hidden if no sleep data has been collected for the last week. - Added a pin to the timeline when a long walk or run is completed, displaying distance, calorie burn and duration. - Improved step-counting algorithm for Pebble Health. - Fixed display of jumboji for larger font sizes. - Fixed bug causing app resources not to be removed when uninstalling an application. - Fixed watch reset on disconnecting smartstrap during communication. - Fixed replying to SMS on iOS when a notification contains an attachment. - Improved low battery screen and icons. - Improved rendering of upper-case accented characters. - Fixed watch crash caused by invalid ANCS messages that occur when two notification-receiving devices are connected. - Fixed bug causing the "Dismiss" action to delete voicemails when a "Dismiss" option is not available for the notification in iOS. - Fixed never-ending phone calls on iOS8 when a second phone call is received before the first call is ended. - Fixed a watch crash when using the "Send Text" app caused by an animation not being cleaned up. ### Changes to SDK - Added new [`gdraw_command_frame_get_command_list`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_frame_get_command_list "gdraw\_command\_frame\_get\_command\_list") API. - Fixed project build failures when the project path contained unicode. ### Changes to Documentation - Added documentation of method arguments for `smartstrap_set_timeout`, `smartstrap_service_is_available`, `smartstrap_attribute_get_service_id`, `smartstrap_attribute_get_attribute_id`, and `smartstrap_attribute_end_write`. - Improved description of [`CompassHeadingData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData "CompassHeadingData") and how to convert to a heading using [`CompassHeadingData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData "CompassHeadingData"). --- # Pebble SDK 3.10.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.10.1/ _Release Date: March 10 2016_ This is a hotfix for [SDK 3.10](https://developer.repebble.com/sdk/changelogs/3.10.md). ### Changes to Firmware - Restored the following emoji that were inadvertently removed: - U+2192 RIGHTWARDS ARROW: → - U+25BA BLACK RIGHT-POINTING POINTER: ► - U+2605 BLACK STAR: ★ - U+1F3A4 MICROPHONE: 🎤 - U+1F3A5 MOVIE CAMERA: 🎥 - U+1F435 MONKEY FACE: 🐵 - U+1F4AA FLEXED BICEPS: 💪 - U+1F4F7 CAMERA: 📷 - U+1F648 SEE-NO-EVIL MONKEY: 🙈 - U+1F3B5 MUSICAL NOTE: 🎵 - U+1F381 WRAPPED PRESENT: 🎁 - Note that these emoji are only available on Time-series Pebbles due to hardware constraints. - Made another attempt at fixing the charging modals. Third time's the charm! ### Changes to SDK None. --- # Pebble SDK 3.10 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.10/ _Release Date: March 7 2016_ ### Changes to Firmware - Added energy (Calorie) usage to the Health app. - Changed "till" to "'til" in the low battery modals. - [`rand`](https://developer.repebble.com/docs/c/Standard_C/Math.md#rand "rand") is now seeded from the hardware RNG on app start, as it was in firmware 3.4 and earlier. - Notifications containing only an emoji will now fill the screen with that emoji, if that emoji supports this. - Added support for filtering notifications by app on iOS (requires iOS app 3.10). - Fixed a window stack crash affecting some 2.x apps running on 3.9 watches. - Fixed an error on Pebble Classic and Pebble Steel where reminders did not include their description. - An issue causing the sleep graph to sometimes be blank on the deep sleep display of the Health app was resolved. - Improved Health accuracy. - Improved firmware stability. ### Changes to SDK - Added support for debugging apps with gdb, in conjunction with pebble tool 4.2. - Added [`health_service_get_measurement_system_for_display`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_measurement_system_for_display "health\_service\_get\_measurement\_system\_for\_display") to retrieve the user's unit preference. - Added [`health_service_sum_averaged`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged "health\_service\_sum\_averaged") and [`health_service_metric_averaged_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_averaged_accessible "health\_service\_metric\_averaged\_accessible") to access average health data. These can be used to determine the goal line used by the Pebble Health app. - Added [`HealthMetricRestingKCalories`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricRestingKCalories "HealthMetricRestingKCalories") and [`HealthMetricActiveKCalories`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricActiveKCalories "HealthMetricActiveKCalories") to retrieve Calorie burn information from Pebble Health. - `CFLAGS` and `LINKFLAGS` env variables are now correctly honored by the app build process. - JSON files under the `src/js/` directory are available to `require` in newly-created projects. - [`persist_write_bool`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_bool "persist\_write\_bool") and [`persist_write_int`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_int "persist\_write\_int") are now correctly documented to return the number of bytes written on success. --- # Pebble SDK 3.10-beta6 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.10-beta6/ _Release Date: March 4 2016_ This is a pre-release SDK, containing a preview of the new Pebble Health API. The following release notes list only changes since [3.10-beta2](https://developer.repebble.com/sdk/changelogs/3.10-beta2.md). ### Changes to Firmware - Improved Health accuracy. - Improved firmware stability. ### Changes to SDK - Added information necessary to debug workers as well as apps. - Added information about SDK calls to gdb. --- # Pebble SDK 3.10-beta2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.10-beta2/ _Release Date: February 19 2016_ This is a pre-release SDK, containing a preview of the new Pebble Health API. The following release notes list only changes since [3.10-beta1](https://developer.repebble.com/sdk/changelogs/3.10-beta1.md). ### Changes to Firmware - [`health_service_get_measurement_system_for_display`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_measurement_system_for_display "health\_service\_get\_measurement\_system\_for\_display") no longer crashes when called on real watches. - [`rand`](https://developer.repebble.com/docs/c/Standard_C/Math.md#rand "rand") is now seeded from the hardware RNG on app start, as it was in firmware 3.4 and earlier. - An issue causing the sleep graph to sometimes be blank on the deep sleep display of the Health app was resolved. ### Changes to SDK - `CFLAGS` and `LINKFLAGS` env variables are now correctly honored by the app build process. - JSON files under the `src/js/` directory are available to `require` in newly-created projects. - [`persist_write_bool`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_bool "persist\_write\_bool") and [`persist_write_int`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_int "persist\_write\_int") are now correctly documented to return the number of bytes written on success. --- # Pebble SDK 3.10-beta1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.10-beta1/ _Release Date: February 15 2016_ This is a pre-release SDK, containing a preview of the new Pebble Health API. ### Changes to Firmware - Added energy (Calorie) usage to the Health app. - Changed "till" to "'til" in the low battery modals. - Improved firmware stability. ### Changes to SDK - Added [`health_service_get_measurement_system_for_display`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_measurement_system_for_display "health\_service\_get\_measurement\_system\_for\_display") to retrieve the user's unit preference. - Added [`health_service_sum_averaged`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged "health\_service\_sum\_averaged") and [`health_service_metric_averaged_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_averaged_accessible "health\_service\_metric\_averaged\_accessible") to access average health data. These can be used to determine the goal line used by the Pebble Health app. - Added [`HealthMetricRestingKCalories`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricRestingKCalories "HealthMetricRestingKCalories") and [`HealthMetricActiveKCalories`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricActiveKCalories "HealthMetricActiveKCalories") to retrieve Calorie burn information from Pebble Health. --- # Pebble SDK 3.9.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.9.2/ _Release Date: February 9 2016_ ### Changes to Firmware - Resolved the backlight not activating on Kickstarter-era Pebbles on which the backlight intensity had never been set. - Fixed a potential crash after updating the firmware with a low battery. ### Changes to SDK - Fixed an error when attempting to `require()` JSON files. --- # Pebble SDK 3.9.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.9.1/ _Release Date: February 4 2016_ ### Changes to Firmware - Fixed a number of DST-related timezone errors introduced in 3.9. ### Changes to SDK None; no SDK was shipped. --- # Pebble SDK 3.9 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.9/ _Release Date: February 3 2016_ ### Changes to Firmware - Resolved confusion caused by manipulating the window stack while it is animating. - Fixed a crash when enabling text pagination on aplite. - Improved the reliability of smartstrap communication when the system is busy. - Fixed calendar pins sometimes omitting the location on aplite. - Reduced the memory overhead for PNG decompression to 1152 bytes (plus space for both the compressed and decompressed versions of the image). - Fixed an issue causing "call back" on missed call pins on iOS to not actually call back. - Fixed an issue causing sleep graphs to not be drawn at all on the deep sleep screen in the Health app unless there was deep sleep to display. - Multiple timezone fixes, including Uruguay and Cancun. - Made system watchface transitions snappier on Pebble Time. - Redesigned the watch-only mode watchface. - Changed the low-battery percentage warnings to time estimates. - Added U+1F3B5 MUSICAL NOTE 🎵 and U+1F381 WRAPPED PRESENT 🎁 to the notification font. - Added an indicator of picture notifications on iOS. - Redesigned Tic-Toc on the Rose Gold Pebble Time Round. - Moved the hands on Tic-Toc on Pebble Time Round one pixel to the left. - Assorted health tracking improvements. - Assorted stability improvements. - Assorted localization improvements. ### Changes to SDK - Added the new [Health API](https://developer.repebble.com/guides/events-and-services/health.md)! - This enables you to read information from Pebble Health and include it in your app or watchface. - Added support for multiple JavaScript files! Check out [the blog post](https://developer.repebble.com/blog/2016/01/29/Multiple-JavaScript-Files.md) for more details. - Fixed an issue introduced in 3.8.2 causing some _successful_ font builds to hang the build process indefinitely. - Added [`PBL_IF_MICROPHONE_ELSE`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#PBL_IF_MICROPHONE_ELSE "PBL\_IF\_MICROPHONE\_ELSE"), `PBL_IF_SMARTSTRAP_ELSE`, and [`PBL_IF_HEALTH_ELSE`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#PBL_IF_HEALTH_ELSE "PBL\_IF\_HEALTH\_ELSE") macros, for consistency with [`PBL_IF_COLOR_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE "PBL\_IF\_COLOR\_ELSE") and friends. This also includes new `PBL_MICROPHONE`, `PBL_SMARTSTRAP`, and `PBL_HEALTH` defines. --- # Pebble SDK 3.9-beta8 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.9-beta8/ _Release Date: February 1 2016_ This is a pre-release SDK, containing a preview of the new Pebble Health API. **Important note** : We have changed the values of [`HealthServiceAccessibilityMask`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask "HealthServiceAccessibilityMask"). This is not a binary compatible change, so you _must_ recompile watchfaces that use [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") or [`health_service_any_activity_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_any_activity_accessible "health\_service\_any\_activity\_accessible"). Additionally, [`HealthServiceAccessibilityMaskAvailable`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMaskAvailable "HealthServiceAccessibilityMaskAvailable") is no longer zero, so code that tried to compare with zero will need to be updated. The following release notes list only changes since [3.9-beta7](https://developer.repebble.com/sdk/changelogs/3.9-beta7.md). ### Changes to Firmware - Fixed [`health_service_get_minute_history`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_minute_history "health\_service\_get\_minute\_history") sometimes returning empty minutes. - Fixed a bug causing the backlight to get stuck on at full intensity. - Reduced watchface transition animation choppiness. - Improved firmware stability. ### Changes to SDK - Changed the values of ``HealthServiceAccessibilityMask` so that [`HealthServiceAccessibilityMaskAvailable`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMaskAvailable "HealthServiceAccessibilityMaskAvailable") is nonzero. This is an ABI incompatible change, and watchfaces must be recompiled with beta8 to run correctly on beta8 or later firmware. - Added support for `require`ing a JSON file from PebbleKit JS, if the wscript is adjusted appropriately. --- # Pebble SDK 3.9-beta7 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.9-beta7/ _Release Date: January 29 2016_ This is a pre-release SDK, containing a preview of the new Pebble Health API. The following release notes list only changes since [3.9-beta5](https://developer.repebble.com/sdk/changelogs/3.9-beta5.md) ### Changes to Firmware - Fixed a crash when calling [`health_service_any_activity_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_any_activity_accessible "health\_service\_any\_activity\_accessible"). - Ensured that [`HealthEventSignificantUpdate`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventSignificantUpdate "HealthEventSignificantUpdate") is called immediately when subscribing to the health service, as documented. - Fixed a failure to correctly read the ambient light sensor. - Further watchface transition tweaks. - Improved firmware stability. - Improved bluetooth reliability. ### Changes to SDK - `HealthLightLevel` was renamed to [`AmbientLightLevel`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevel "AmbientLightLevel") - Added `PBL_HEALTH` and `PBL_IF_HEALTH_ELSE` macros. - Added support for multiple JavaScript files! Check out [the blog post](https://developer.repebble.com/blog/2016/01/29/Multiple-JavaScript-Files.md) for more details. --- # Pebble SDK 3.9-beta5 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.9-beta5/ _Release Date: January 19 2016_ This is a pre-release SDK, containing a preview of the new Pebble Health API. ### Known Issues - `HealthLightLevel` will be renamed to [`AmbientLightLevel`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevel "AmbientLightLevel") before SDK 3.9 ships. - Bluetooth may be more unreliable in this beta release. ### Changes to Firmware - Resolved confusion caused by manipulating the window stack while it is animating. - Fixed a crash when enabling text pagination on aplite. - Improved the reliability of smartstrap communication when the system is busy. - Fixed calendar pins sometimes omitting the location on aplite. - Reduced the memory overhead for PNG decompression to 1152 bytes (plus space for both the compressed and decompressed versions of the image). - Fixed an issue causing "call back" on missed call pins on iOS to not actually call back. - Fixed an issue causing sleep graphs to not be drawn at all on the deep sleep screen in the Health app unless there was deep sleep to display. - Multiple timezone fixes, including Uruguay and Cancun. - Made system watchface transitions snappier on Pebble Time. - Redesigned the watch-only mode watchface. - Changed the low-battery percentage warnings to time estimates. - Added U+1F3B5 MUSICAL NOTE 🎵 and U+1F381 WRAPPED PRESENT 🎁 to the notification font. - Added an indicator of picture notifications on iOS. - Redesigned Tic-Toc on the Rose Gold Pebble Time Round. - Moved the hands on Tic-Toc on Pebble Time Round one pixel to the left. - Assorted health tracking improvements. - Assorted stability improvements. - Assorted localization improvements. ### Changes to SDK - Added the new [Health API](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService")! - This enables you to read information from Pebble Health and include it in your app or watchface. - We are interested in feedback on this API! Let us know what you think on the [developer forum](https://forum.repebble.com/c/developers-ask-questions-and-get-help/7)! - Fixed an issue introduced in 3.8.2 causing some _successful_ font builds to hang the build process indefinitely. - Added [`PBL_IF_MICROPHONE_ELSE`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#PBL_IF_MICROPHONE_ELSE "PBL\_IF\_MICROPHONE\_ELSE") and `PBL_IF_SMARTSTRAP_ELSE` macros, for consistency with [`PBL_IF_COLOR_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE "PBL\_IF\_COLOR\_ELSE") and friends. --- # Pebble SDK 3.8.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.8.2/ _Release Date: December 21 2015_ This release also includes the changes from [3.8.1](https://developer.repebble.com/sdk/changelogs/3.8.1.md), which was not shipped on physical watches. ### Changes to Firmware - Fixed a crash when trying to unload a cached app. - Fixed a crash showing health pins when using a non-English language pack. ### Changes to SDK - Fixed an issue causing font processing failures to lead to the build system hanging until forcibly terminated. --- # Pebble SDK 3.8.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.8.1/ _Release Date: December 16 2015_ This release is currently SDK-only. In particular, the timeline text color fix will not be available on watches until we ship a new firmware build ~~later this~~ next week. ### Changes to Firmware - Fixed a timeline crash that only occurred on Aplite when run in an SDK emulator in a release build. - Fixed timeline `foregroundColor` being used inappropriately in the timeline list view, sometimes leading to white-on-white text. ### Changes to SDK - None --- # Pebble SDK 3.8 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.8/ _Release Date: December 15 2015_ ### Changes to Firmware - Added support for Pebble Classic and Pebble Steel watches! - You can read more [on our blog](https://developer.repebble.com/blog/2015/12/02/Bringing-the-Family-Back-Together.md) - This is a major change! Please test your aplite apps thoroughly after rebuilding with SDK 3.8. - Added Pebble Health! - No API is yet available to access health information, but one will be added in an upcoming release. - Improved the rendering of [`NumberWindow`](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md "NumberWindow"). - Fixed a race when exiting an [`ActionMenuLevel`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuLevel "ActionMenuLevel"). - Timeline pins no longer require icons, and will pick a sane default. - Resolved an issue causing alarms to not vibrate in standby mode - Added `shortSubtitle` property to all timeline layouts. - This string, if provided, will be used as the subtitle in the timeline list view. - Added a `displayTime` enum to the timeline weather layout. - If `none`, it will not display a time. If `pin`, it will display the time of the pin in the title. The default is `pin`. - Timeline pins were no longer last updated in 1970. - Resolved a memory leak in [`DictationSession`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession "DictationSession"). - Restored the double vibration when _disabling_ Quiet Time (enabling is still a single vibration). - Restored the missing "Call back" button to iOS missed call pins. - Assorted minor design changes. ### Changes to SDK - Deprecated `png`, `pbi` and `pbi8` in favor of a new `bitmap` resource type. [Read more on our blog!](https://developer.repebble.com/blog/2015/12/02/Bitmap-Resources.md). - Added `PBL_MICROPHONE` and `PBL_SMARTSTRAP` macros, which will be defined when a microphone or a smartstrap connector is available, respectively. Note that the runtime availability of the services should still be tested, as the presence of hardware is not sufficient. - Font resources are now somewhat smaller than they used to be. ### Changes to Pebble Tool SDK 3.8 is the first version exclusively available via the [new pebble tool](https://developer.repebble.com/blog/2015/12/01/A-New-Pebble-Tool.md). No future SDK packages will contain the tool, or any changes to the tool. ### Changes to Emulator/Phonesim As with the pebble tool, the emulator and phone simulator are no longer coupled to the SDK. --- # Pebble SDK 3.8-beta12 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.8-beta12/ _Release Date: December 14 2015_ This is a pre-release version of the Pebble SDK. Only changes since beta10 are included. ### Changes to Firmware - [`graphics_fill_radial`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_radial "graphics\_fill\_radial") will now produce dithered 50% gray on Aplite where appropriate. - Fixed an issue causing reduced stack availability after using dictation. - Slightly reduced stack usage in general. - Added support for 3.8 to 2.9 downgrades (but no data is retained when doing this). - Restored the missing "Call back" button to iOS missed call pins. - Improved stability. ### Changes to SDK - Fixed an issue causing SDK 3.8 apps to install on firmware 3.6 and 3.7, but then potentially crash at runtime. They now display a compatibility error when launched. --- # Pebble SDK 3.8-beta10 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.8-beta10/ _Release Date: December 9 2015_ This is a pre-release version of the Pebble SDK. Only changes since beta8 are included. ### Changes to Firmware - Resolved an issue causing alarms to not vibrate in standby mode - Added `shortSubtitle` property to all timeline layouts. - This string, if provided, will be used as the subtitle in the timeline list view. - Added a `displayTime` enum to the timeline weather layout. - If `none`, it will not display a time. If `pin`, it will display the time of the pin in the title. The default is `pin`. - Timeline pins were no longer last updated in 1970. - Resolved some minor layout issues. ### Changes to SDK - Added `PBL_MICROPHONE` and `PBL_SMARTSTRAP` macros, which will be defined when a microphone or a smartstrap connector is available, respectively. Note that the runtime availability of the services should still be tested, as the presence of hardware is not sufficient. - Removed spuriously exported `_EMOJI` fonts. Emoji symbols will display in system fonts as before. - Fixed an issue causing the generation of indexed PBIs containing non-black transparent pixels to fail. --- # Pebble SDK 3.8-beta8 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.8-beta8/ _Release Date: December 2 2015_ This is a pre-release version of the Pebble SDK. ### Changes to Firmware - Added support for Pebble Classic and Pebble Steel watches! - You can read more [on our blog](https://developer.repebble.com/blog/2015/12/02/Bringing-the-Family-Back-Together.md) - This is a major change! Please test your aplite apps thoroughly after rebuilding with SDK 3.8. - Improved the rendering of [`NumberWindow`](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md "NumberWindow"). - Fixed a race when exiting an [`ActionMenuLevel`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuLevel "ActionMenuLevel"). - Timeline pins no longer require icons, and will pick a sane default. - Restored the double vibration when _disabling_ Quiet Time (enabling is still a single vibration). - Resolved a memory leak in [`DictationSession`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession "DictationSession"). - Assorted minor design changes. ### Changes to SDK - Deprecated `png`, `pbi` and `pbi8` in favor of a new `bitmap` resource type. [Read more on our blog!](https://developer.repebble.com/blog/2015/12/02/Bitmap-Resources.md). - Font resources are now somewhat smaller than they used to be. ### Changes to Pebble Tool SDK 3.8 is the first version exclusively available via the [new pebble tool](https://developer.repebble.com/blog/2015/12/01/A-New-Pebble-Tool.md). No future SDK packages will contain the tool, or any changes to the tool. ### Changes to Emulator/Phonesim As with the pebble tool, the emulator and phone simulator are no longer coupled to the SDK. --- # Pebble SDK 3.7 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.7/ _Release Date: November 23 2015_ Firmware 3.7 will not appear to be available unless you have the latest version of the iOS or Android app. ### Changes to Firmware - Added support for SMS notification replies on iOS, if you use AT&T. - Added the date to the icon in calendar timeline pins. - Restored the reminder icon to reminder notifications on Pebble Time Round. - Fixed text clipping in the incoming call window in some cases. - Improved rendering of clipped, antialiased GPaths. ### Changes to SDK - Resolved an issue present since SDK 3.3 causing JavaScript changes to be ignored by incremental builds. ### Changes to Pebble Tool - None. ### Changes to Emulator/Phonesim - Added an option to block access to local IP addresses to the phonesim. This blocking is only active on CloudPebble. --- # Pebble SDK 3.6.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.6.2/ _Release Date: November 4 2015_ This SDK corresponds with firmware 3.6.2, which only shipped on Pebble Time Round. ### Changes to Firmware - Action bars are now less curved on Chalk. - Added Chalk support to the sports API. - Improved text rendering in unfocused menu layer cells on Chalk. - Some missing animations were added on Chalk. - Resolved an issue with alarms when DST starts or ends. ### Changes to SDK - The recommended menu layer heights on Chalk have changed. [`MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT "MENU\_CELL\_ROUND\_FOCUSED\_SHORT\_CELL\_HEIGHT"), [`MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT "MENU\_CELL\_ROUND\_FOCUSED\_TALL\_CELL\_HEIGHT"), [`MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT "MENU\_CELL\_ROUND\_UNFOCUSED\_SHORT\_CELL\_HEIGHT") and [`MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT "MENU\_CELL\_ROUND\_UNFOCUSED\_TALL\_CELL\_HEIGHT") were updated accordingly. ### Changes to Pebble Tool - Resolved an issue causing emulator launches that raise EINTR to fail. ### Changes to Emulator/Phonesim - None. --- # Pebble SDK 3.6 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.6/ _Release Date: October 14 2015_ This changelog contains all changes since SDK 3.4. SDK 3.5 was never shipped. ### Known Issues - Unfocused menu layer cells on Chalk may have their content inappropriately truncated. This behavior will improve before Pebble Time Round is released. ### Changes to Firmware - Added support for [circular watches](https://developer.repebble.com/guides/user-interfaces/round-app-ui.md). - Window stack animations were disabled on Chalk. - Adjusted the display of [`ActionBarLayers`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") for circular watches. - Adjusted the display of [`ActionMenus`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md "ActionMenu") for circular watches. - Increased the height of the [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer") on Chalk (16 to 24 pixels). - Improved backlight behavior when dictating text. - Added support for 8 KiB AppMessage buffers. ### Changes to SDK - Added the new "Chalk" platform for the Pebble Time Round. - Added the [Dictation API](https://developer.repebble.com/guides/pebble-apps/sensors/dictation/), enabling voice input on the Basalt and Chalk platforms. - Added `PBL_ROUND`, `PBL_RECT` and `PBL_PLATFORM_CHALK` defines. - Added [`PBL_IF_ROUND_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_ROUND_ELSE "PBL\_IF\_ROUND\_ELSE"), [`PBL_IF_RECT_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_RECT_ELSE "PBL\_IF\_RECT\_ELSE"), [`PBL_IF_COLOR_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE "PBL\_IF\_COLOR\_ELSE") and [`PBL_IF_BW_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_BW_ELSE "PBL\_IF\_BW\_ELSE") macros. [`COLOR_FALLBACK`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#COLOR_FALLBACK "COLOR\_FALLBACK") is now considered deprecated. - Added [`graphics_fill_radial`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_radial "graphics\_fill\_radial") and [`graphics_draw_arc`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_arc "graphics\_draw\_arc"), for drawing partial circles. - Added [`gpoint_from_polar`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#gpoint_from_polar "gpoint\_from\_polar") and [`grect_centered_from_polar`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#grect_centered_from_polar "grect\_centered\_from\_polar") for conversion from polar to cartesian coordinates. - Added [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator"), which provides a visual indicator of content above or below the display. - Added [`menu_layer_set_center_focused`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_center_focused "menu\_layer\_set\_center\_focused"), which forces the highlighted menu item to always be in the center of the display. This is the default configuration for menus on Chalk. - Added [`menu_layer_is_index_selected`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_is_index_selected "menu\_layer\_is\_index\_selected"). - Added constants for the heights of menu cells on Chalk: [`MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT "MENU\_CELL\_ROUND\_FOCUSED\_SHORT\_CELL\_HEIGHT"), [`MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT "MENU\_CELL\_ROUND\_FOCUSED\_TALL\_CELL\_HEIGHT"), [`MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT "MENU\_CELL\_ROUND\_UNFOCUSED\_SHORT\_CELL\_HEIGHT") and [`MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT "MENU\_CELL\_ROUND\_UNFOCUSED\_TALL\_CELL\_HEIGHT"). - Added [`DEG_TO_TRIGANGLE`](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE "DEG\_TO\_TRIGANGLE"), the inverse of the pre-existing [`TRIGANGLE_TO_DEG`](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIGANGLE_TO_DEG "TRIGANGLE\_TO\_DEG"). - Added [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular"), the new framebuffer format used on Chalk. - Added [`gbitmap_get_data_row_info`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info "gbitmap\_get\_data\_row\_info"), because [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular") does not have a constant number of bytes per row. This should now be used for all framebuffer manipulation. - Added the [`GTextAttributes`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAttributes "GTextAttributes") structure, with methods for defining text flow and paging on a circular display. - Added support for [`GTextAttributes`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAttributes "GTextAttributes") to [`graphics_draw_text`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_draw_text "graphics\_draw\_text"). That `NULL` parameter that you've been tacking on the end for two and a half years now actually does something. - Added methods to [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") for flowing and paging text on a circular display. - Added paging support to [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer"). ### Changes to Pebble Tool - Added support for Chalk and circular displays. - Added the [`pebble transcribe` command](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md), enabling testing dictation in the emulator. - Added the [`pebble data-logging` command](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md), to download stored datalogging from the watch to the computer. - Added the [`pebble emu-time-format` commmand](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md), allowing switching of the emulator between 12-hour and 24-hour formats. ### Changes to Emulator/Phonesim - Added support for Chalk and circular displays. - Fixed a JavaScript runtime crash when sending non-ASCII characters by AppMessage. --- # Pebble SDK 3.6-dp8 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.6-dp8/ _Release Date: October 9 2015_ This changelog covers only changes since 3.6-dp7. ### Known Issues - Unfocused menu layer cells on Chalk may have their content inappropriately truncated. This behavior will improve before the final 3.6 release. ### Changes to Firmware - Fixed an issue causing the voice API to not return a transcription when confirmation is enabled. ### Changes to SDK - None. ### Changes to Pebble Tool - None. ### Changes to Emulator/Phonesim - None. --- # Pebble SDK 3.6-dp7 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.6-dp7/ _Release Date: October 9 2015_ This changelog covers only changes since 3.6-dp6. ### Known Issues - Unfocused menu layer cells on Chalk may have their content inappropriately truncated. This behavior will improve before the final 3.6 release. ### Changes to Firmware - MenuLayers will now correctly enable center focus by default on Chalk. - Resolved an issue causing iOS notifications to cease after invoking the "call back" action. - Pebble Time Round no longer vibrates when charging is completed. - The action menu nub is now slightly larger on Pebble Time Round. ### Changes to SDK - [`DEG_TO_TRIGANGLE`](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE "DEG\_TO\_TRIGANGLE") no longer inappropriately normalizes angles. ### Changes to Pebble Tool - None. ### Changes to Emulator/Phonesim - Fixed a JavaScript runtime crash when sending non-ASCII characters by AppMessage. --- # Pebble SDK 3.6-dp6 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.6-dp6/ _Release Date: October 7 2015_ This changelog covers only changes since 3.6-dp5. ### Known Issues - Unfocused menu layer cells on Chalk may have their content inappropriately truncated. This behavior will improve before the final 3.6 release. ### Changes to Firmware - Added window stack transition animations on Chalk (subject to change). - Correctly centred the loading bar for the voice API on Chalk. - Removed an unexpected artifact at the end of ActionMenus on Chalk. ### Changes to SDK - Added [`menu_layer_is_index_selected`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_is_index_selected "menu\_layer\_is\_index\_selected"). - Added constants for the heights of menu cells on Chalk. ### Changes to Pebble Tool - None. ### Changes to Emulator/Phonesim - Fixed a series of issues causing AppMessages larger than 2048 bytes to be lost. --- # Pebble SDK 3.6-dp5 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.6-dp5/ _Release Date: October 2 2015_ This changelog covers only changes since 3.6-dp2. Developer previews 3 and 4 were never shipped. ### Changes to Firmware - Made the status bar on Chalk (Pebble Time Round) four pixels taller (from 20 to 24 pixels). - Resolved an issue causing AppMessages to sometimes be dropped when sending both ways. - Improved backlight behaviour when dictating text. - Fixed a calculation error in [`grect_centered_from_polar`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#grect_centered_from_polar "grect\_centered\_from\_polar"). - Resolved multiple issues that occured when reusing a [`DictationSession`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession "DictationSession"). ### Changes to SDK - Added the [`GTextAttributes`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAttributes "GTextAttributes") structure, with methods for defining text flow and paging on a circular display. - Added support for [`GTextAttributes`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAttributes "GTextAttributes") to [`graphics_draw_text`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_draw_text "graphics\_draw\_text"). That `NULL` parameter that you've been tacking on the end for two and a half years now actually does something. - Added methods to [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") for flowing and paging text on a circular display. - Added paging support to [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer"). - Improved the return codes from the dictation API. ### Changes to Pebble Tool - Added the [`pebble transcribe` command](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md), enabling testing dictation in the emulator. - Added the [`pebble data-logging` command](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md), to download stored datalogging from the watch to the computer. ### Changes to Emulator/Phonesim - Added a translucent ring around the inside of the emulator to remind you of masked pixels. - Resolved an issue causing appmessage events to fire repeatedly. - Resolved an issue causing app installs to frequently fail on Linux. - Resolved an issue causing emulated Bluetooth to be broadly unreliable. --- # Pebble SDK 3.6-dp2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.6-dp2/ _Release Date: September 23 2015_ This changelog collects only changes visible to developers using the emulator. A complete changelog will be provided when 3.6 ships on physical watches. ### Changes to Firmware - Added support for [circular watches](https://developer.repebble.com/guides/user-interfaces/round-app-ui.md). - Window stack animations were disabled on Chalk. - Adjusted the display of [`ActionBarLayers`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") for circular watches. - Adjusted the display of [`ActionMenus`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md "ActionMenu") for circular watches. - Increased the height of the [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer") on Chalk (16 to 20 pixels). ### Changes to SDK - Added the new "Chalk" platform for the Pebble Time Round. - Added `PBL_ROUND`, `PBL_RECT` and `PBL_PLATFORM_CHALK` defines. - Added [`PBL_IF_ROUND_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_ROUND_ELSE "PBL\_IF\_ROUND\_ELSE"), [`PBL_IF_RECT_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_RECT_ELSE "PBL\_IF\_RECT\_ELSE"), [`PBL_IF_COLOR_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE "PBL\_IF\_COLOR\_ELSE") and [`PBL_IF_BW_ELSE`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_BW_ELSE "PBL\_IF\_BW\_ELSE") macros. [`COLOR_FALLBACK`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#COLOR_FALLBACK "COLOR\_FALLBACK") is now considered deprecated. - Added [`graphics_fill_radial`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_radial "graphics\_fill\_radial") and [`graphics_draw_arc`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_arc "graphics\_draw\_arc"), for drawing partial circles. - Added [`gpoint_from_polar`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#gpoint_from_polar "gpoint\_from\_polar") and [`grect_centered_from_polar`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#grect_centered_from_polar "grect\_centered\_from\_polar") for conversion from polar to cartesian coordinates. - Added [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator"), which provides a visual indicator of content above or below the display. - Added [`menu_layer_set_center_focused`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_center_focused "menu\_layer\_set\_center\_focused"), which forces the highlighted menu item to always be in the center of the display. This is the preferred configuration for menus on Chalk. - Added [`DEG_TO_TRIGANGLE`](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE "DEG\_TO\_TRIGANGLE"), the inverse of the pre-existing [`TRIGANGLE_TO_DEG`](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIGANGLE_TO_DEG "TRIGANGLE\_TO\_DEG"). - Added [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular"), the new framebuffer format used on Chalk. - Added [`gbitmap_get_data_row_info`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info "gbitmap\_get\_data\_row\_info"), because [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular") does not have a constant number of bytes per row. This should now be used for all framebuffer manipulation. ### Changes to Pebble Tool - Added support for Chalk and circular displays. ### Changes to Emulator/Phonesim - Added support for Chalk and circular displays. --- # Pebble SDK 3.4 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.4/ _Release Date: September 9 2015_ ### Changes to Firmware - Improved calendar and reminder pin display. - Added support for internationalization and localization. - Added standby mode (disables bluetooth when the watch is not being worn). - This can be toggled in Settings \> System \> Stand-by Mode. - Renamed "Do Not Disturb" to "Quiet Time" and expanded functionality. - Can now disable notifications during calendar events. - Holding back from a watchface will now toggle Quiet Time. - Quiet Time can be enabled from notifications. - Increased timeline paragraph length limit. ### Changes to SDK - Enabled [timeline HTTP actions](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) (requires iOS app 3.2 or Android app 3.4). - Added [Smartstrap APIs](https://developer.repebble.com/guides/smartstraps.md). ### Changes to Pebble Tool - Added the [`pebble emu-control` command](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md), which enables sending real-time accelerometer and compass data from phones to the pebble emulator. ### Changes to Emulator/Phonesim None. --- # Pebble SDK 3.3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.3/ _Release Date: August 19 2015_ ### Changes to Firmware - Screenshots once again work reliably. The display will now freeze briefly while taking a screenshot. - Added "relationship bars" to the timeline, which indicate whether events overlap, are back-to-back, or have time between them. - Added the date to timeline day separators. - Added groundwork for improved Android notification behavior (requires a future Android update). - Further improved rendering of long paragraphs in timeline pins. - Fixed MenuLayer animations in non-full-screen menu layers. - Fixed a crash when passing [`data_logging_log`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#data_logging_log "data\_logging\_log") a NULL pointer. - Fixed timezone error in Chile. - Resolved a privilege escalation exploit. ### Changes to SDK - Added [`ActionMenu`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md "ActionMenu"), for easy hierarchical menus as seen in notifications, timeline, and throughout the system UI. - Added [`app_focus_service_subscribe_handlers`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#app_focus_service_subscribe_handlers "app\_focus\_service\_subscribe\_handlers"), which enables apps to better synchronize with system animations. - Exported [`property_animation_update_gcolor8`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gcolor8 "property\_animation\_update\_gcolor8"), for animating between colors. ### Changes to Pebble Tool None. ### Changes to Emulator/Phonesim None. ### Changes to Documentation None. --- # Pebble SDK 3.2.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.2.1/ _Release Date: August 12 2015_ This is a hotfix release for [SDK 3.2](https://developer.repebble.com/sdk/changelogs/3.2.md). ### Known issues - Taking screenshots from physical watches running firmware 3.2 or 3.2.1 usually doesn't work. We expect to fix this in firmware 3.3. ### Changes to Firmware - Apps compiled using SDK versions earlier than 3.2 which caused heap corruption but did not crash will now emit warnings instead of crashing. Apps compiled using SDK 3.2 or later will crash when a corrupt heap is detected. - Resolved an issue causing emojis to display as boxes when replying to a notification using the large notification font. - Resolved multiple issues caused by having unsupported language packs installed. ### Changes to the SDK None. ### Changes to the Pebble Tool - Fixed a crash in the pebble tool when receiving a log containing a non-ASCII character. ### Changes to the emulator and phone simulator - Fixed JavaScript apps crashing when attempting to log non-ASCII characters. ### Changes to documentation None. --- # Pebble SDK 3.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.2/ _Release Date: July 22 2015_ ### Known issues - Taking screenshots from physical watches running firmware 3.2 usually doesn't work. We expect to fix this in firmware 3.3. ### Changes to Firmware - Improved the reliability of app wakeup service when it is in use by multiple apps. - Added generic icons to the Watchfaces app for watchfaces without one. - Improved rendering of non-antialiased rounded rectangles. - Improved rendering of timeline pins with long body text. - Improved rendering of notifications. - Improved rendering of timeline pins using the Reminder layout. - Improved the music app on Android, in conjunction with the Pebble Time Android app version 3.2. - Added settings for backlight brightness and duration. - Added settings for notification font size and vibration intensity. - Restored the "dismiss all" action on notifications (long press or use the menu). - Significantly improved music control reliability on iOS. - Improved music control on Android, in conjunction with version 3.2 of the Android app. - Fixed a crash when forcibly terminating the launcher. - Improved the behavior of the app fetch UI after a bluetooth reconnection. ### Changes to SDK - [`ANIMATION_PLAY_COUNT_INFINITE`](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_PLAY_COUNT_INFINITE "ANIMATION\_PLAY\_COUNT\_INFINITE") got lost, but it's back now. - Added [`gcolor_legible_over`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gcolor_legible_over "gcolor\_legible\_over"), which returns a color legible over some background color. ### Changes to Pebble Tool We scrapped everything and rewrote it from scratch! - Introducing [libpebble2](https://github.com/coredevices/libpebble3), the successor to libpebble. If you want to write tools that interact with a watch, this is the library to use. - Also introducing [pebble-tool](https://github.com/pebble/pebble-tool), the successor to the old 'pebble' command, which was also part of libpebble. The following user-visible changes were made: - You can now connect to your phone via the CloudPebble proxy, even if the network blocks local connections. Pass `--cloudpebble` to do this. - The pebble tool will now prompt you to opt in to analytics on first launch. While this is optional, it would be very helpful if you said yes. - If you had previously opted out, that will still be respected and the question will be skipped. - Analytics are no longer collected synchronously, which should reduce pauses while using the pebble tool. - Launching and connecting to the emulator should now be much more reliable. - Both aplite and basalt emulators can now run concurrently. - There is no longer a default action if no connection is specified. However, if exactly one emulator is running, that will be used by default. - Screenshots are now color-corrected by default. Use `--no-correction` to disable correction. - You can now pass a filename to the screenshot command. - On OS X, screenshots will automatically open once they are taken. This can be disabled by passing `--no-open`. - Screenshots now display a progress bar while the are taken. - `--debug` and `--debug-phonesim` have both been removed. They have been replaced by `-v`. - You can use up to four v's (`-vvvv`) for increased verbosity. At this level, all protocol messages will be displayed in both deserialised and binary forms. - `pebble build` now respects `-v` (up to three v's) to increase the verbosity of build output. - `pebble build` can now pass arguments to your wscript; place them after a `--`. - Log output from `pebble logs` and `pebble install --logs` is now in color. - Added `pebble logout`. - `pebble wipe` now only wipes data for the current SDK version, and will not log you out. - To wipe _everything_, use `pebble wipe --everything`. - `pebble app-config` has been renamed to `pebble emu-app-config`. - The following commands have been removed: `list`, `rm`, `current`, `uuids`, `coredump`. - The environment produced by `pebble repl` has changed: it now provides a libpebble2 [`PebbleConnection`](http://libpebble2.readthedocs.org/en/v0.0.7/connection/#libpebble2.communication.PebbleConnection) object named `pebble` and places the pebble protocol messages under `protocol`. - Lightblue support was removed, but direct serial connections are still available using `--serial`, e.g. `--serial /dev/cu.PebbleTimeDF7C-SerialPo`. ### Changes to Emulator/Phonesim - Updated the phone simulator to use libpebble2. ### Changes to Documentation - [PebbleKit JS is now documented](https://developer.repebble.com/docs/pebblekit-js.md). - [A PebbleKit Android tutorial was added](https://developer.repebble.com/getting-started/android-tutorial/part1/). - [A PebbleKit iOS tutorial was added](https://developer.repebble.com/getting-started/ios-tutorial/part1/). --- # Pebble SDK 3.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.1/ _Release Date: June 30 2015_ ### Changes to Firmware - Fix watch reset on calling [`compass_service_subscribe`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_subscribe "compass\_service\_subscribe") from a worker. - Fix bug where setting click config is ignored if a notification is displayed. - Fix app crash on calling [`gdraw_command_sequence_destroy`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_destroy "gdraw\_command\_sequence\_destroy"). - Fix bug causing valid PNG8 images with a zero-length `tRNS` chunk to not load. - Fix app crashes on 2.x apps using MenuLayers. - Fix app crashes on 2.x apps using ScrollLayer. - Fix ActionBarLayer being drawn as white when set to GColorClear. - Fix bug causing [`menu_cell_title_draw`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_title_draw "menu\_cell\_title\_draw") and [`menu_cell_basic_header_draw`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_basic_header_draw "menu\_cell\_basic\_header\_draw") to always render text in black. - Fix alarms sometimes crashing the watch after vibrating for ten minutes. - Fix transparent zero-radius circles rendering incorrectly. - Improve rendering of zero-length lines with stroke width greater than one. - Correctly display a sloth after deleting all pins on the timeline. - Improve Bluetooth reliability. - Reduced applog output from the launcher menu. - Fix multiple cells being highlighted when setting the Do Not Disturb time range. - Improve responsiveness when returning to a watchface from the launcher. ### Changes to SDK - `window_set_status_bar_icon` is now deprecated. ### Changes to Emulator/Phonesim - Fix WebSocket connections staying open after closing the app. - Improve reliability of Aplite emulator installs when there are many timeline pins - XMLHttpRequest now correctly returns a Uint8Array instead of ArrayBuffer. ### Changes to Pebble Tool - Add support for `pebble app-config` command. - Modify `pebble rm` command to use --bank or --uuid on 2.x, and --uuid on 3.x - Modify `pebble current`, `pebble list` and `pebble uuids` commands to return a no-op message on 3.x. - Remove login warning when not using emulator/phonesim. - Improve error logging for JSON parsing errors. - Fix a minor analytics bug. - Fix requirements.txt bug. ### Changes to Documentation - Update documentation for `window_set_fullscreen`. - Update documentation for [`clock_to_timestamp`](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_to_timestamp "clock\_to\_timestamp"). - Fix typo in documentation for [`MenuLayerDrawBackgroundCallback`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawBackgroundCallback "MenuLayerDrawBackgroundCallback"). --- # Pebble SDK 3.0 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0/ _Release Date: May 27 2015_ This is the first public release of SDK 3.0. It includes a number of small fixes to polish the timeline ui as well as improve stability. ## Detailed List of Changes: ### Changes for Firmware: - Added a crash dialogue when watchfaces crash. ### Changes to SDK: _No changes_ ### Changes for SDK Tools: - The emulator now supports WebSockets - The emulator now persists JavaScript `localStorage` between emulator launches - The emulator now caches app JavaScript between emulator launches - The SDK no longer depends on cython to install correctly. ### Changes to Timeline: - Fixed a bug where text would overlay in calendar reminders. ### Changes for Examples: _No changes_ ### Changes for Documentation: - Fixed documentation for [`clock_to_timestamp`](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_to_timestamp "clock\_to\_timestamp") to specify that it returns a timestamp in localtime on aplite. --- # Pebble SDK 3.0-beta12 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-beta12/ _Release Date: May 17 2015_ This SDK release includes improvements to stability including fixes for timeline and timezone. There's also a new guide for making your apps compatible on both platforms. ## Detailed List of Changes: ### Changes for Firmware: - Fixed a bug with timezone that would result in reporting the incorrect time. - Ongoing timeline events that started less than 10 minutes ago now show up in the future. ### Changes to SDK: - Fixed `Pebble.getAccountToken` on Android to return the same token as iOS. To learn how to convert a new token to an old one for the same account, read the Migration Guide. ### Changes for SDK Tools: - Fixed a bug with the `pebble analyze-size` command caused by an incorrect elf file location. ### Changes to Timeline: - Fixed a bug where [Reminders](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) would not be shown at the precise time they were set. ### Changes for Examples: - Updated the layout and content of the [Examples](https://developer.repebble.com/examples.md) page. ### Changes for Documentation: - Added a new guide, [Building For Every Pebble](https://developer.repebble.com/guides/best-practices/building-for-every-pebble.md); this covers the best practices for building an app compatible with both platforms. --- # Pebble SDK 3.0-beta11 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-beta11/ _Release Date: May 10 2015_ This is the second beta release of Pebble SDK 3.0. It includes a number of fixes to improve stability as well as new guide for Design and Interaction. ## Detailed List of Changes: ### Changes for Firmware: - Crashes within [`worker`](https://developer.repebble.com/docs/c/Worker.md "Worker") will now show up with a crash dialog on the watch. - Fixed bug where `Timeline` events displayed improper start/finish times. - Fixed bug where images were drawn incorrectly if bounds in [`layer_set_bounds`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds "layer\_set\_bounds") were set differently than (0, 0, size.w, size.h). ### Changes to SDK: - Fixed a bug where apps would fail to wake up because [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup") expected time in UTC. ### Changes for SDK Tools: _No changes_ ### Changes to Timeline: _No changes_ ### Changes for Examples: _No changes_ ### Changes for Documentation: - Added [Design and Interaction](https://developer.repebble.com/guides/design-and-interaction.md) guides. --- # Pebble SDK 3.0-beta10 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-beta10/ _Release Date: April 30 2015_ This is the first beta release of Pebble SDK 3.0. All applications compiled with previous SDK should work on 3.0 and up. However, we have made several changes to the APIs in this release that will require source code changes. We **strongly recommend** that all developers rebuild and retest their apps with this version of the SDK. _All apps built with beta10 will build with future releases of the SDK._ ## Detailed List of Changes: ### Changes for Firmware: - The PDC format has changed. PDCs created for developer preview 9 or earlier will no longer work. ### Changes to SDK: - Add new [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer") that you should use in apps that you want to have a status bar. We've added a section to the 3.0 migration guide with example code and screenshots. - Added `PBL_SDK_2` and `PBL_SDK_3` or detecting the major version of the SDK at compile time. - Added new `buffer_size` parameter to [`clock_get_timezone`](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_get_timezone "clock\_get\_timezone"). - Added Pebble Time Steel models to [`WatchInfoModel`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoModel "WatchInfoModel"). ### Changes for SDK Tools: - The emulator now works on 32 bit Linux machines. - The timeline now works with Python 2.7.9. ### Changes to Timeline: - Apps in sandbox mode no longer have a whitelist by default. Existing timeline apps are not affected. Take a look at the [Enabling the Timeline](https://developer.repebble.com/guides/pebble-timeline.md) guide for further information. - There are more new icons you can use in your pins. Check out the [guide on pin structure](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) for more details. ### Changes for Examples: _No changes_ ### Changes for Documentation: _No changes_ --- # Pebble SDK 3.0-dp9 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp9/ _Release Date: April 27 2015_ This is the ninth Developer Preview release of Pebble SDK 3.0. ## Detailed List of Changes: ### Changes for Firmware: _No changes_ ### Changes to SDK: - Added [`menu_layer_set_normal_colors`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_normal_colors "menu\_layer\_set\_normal\_colors") and [`menu_layer_set_highlight_colors`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_highlight_colors "menu\_layer\_set\_highlight\_colors") to make using [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer")s much simpler. - Renamed `GColorEq` to [`gcolor_equal`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gcolor_equal "gcolor\_equal") to be more consistent with similar methods. - `InverterLayer` has been deprecated and removed from the SDK. ### Changes for SDK Tools: - The pebble tool will now use any running emulator before attempting to launch the default Basalt emulator - Fixed a bug causing an incorrect color for foregroundColor and backgroundColor on timeline pins ### Changes to Timeline: - There are now many more icons you can use in your timeline pins. Check out the [guide on pin structure](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md#pin-icons) for more details. **Note:** All the existing icons have been renamed. ### Changes for Examples: - Deprecated feature-inverter-layer SDK example (see deprecation notice on [GitHub](https://github.com/pebble-examples/feature-inverter-layer)) ### Changes for Documentation: _No changes_ --- # Pebble SDK 3.0-dp8 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp8/ _Release Date: April 16 2015_ This is the eighth Developer Preview release of Pebble SDK 3.0. This week we were focussed on the firmware UI and so this release does not contain many changes that are visible to developers. ## Detailed List of Changes: ### Changes for Firmware: - Updated the timeline UI. - In preparation for the new 3.0 design style, we have removed the old system status bar and all 3.0 apps are now fullscreen by default. We will be releasing a new StatusLayer in the future. ### Changes to SDK: - Added [`Pebble.getActiveWatchInfo()`](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) for getting details about the currently connected Pebble watch. ### Changes for SDK Tools: - Fixed incorrect values when reporting the maximum sizes of apps. - Added SDK emulator support to the [pebble command line tools](https://developer.repebble.com/guides/publishing-tools/pebble-tool) for `emu_tap`, `emu_bt_connection`, `emu_compass`, `emu_battery` and `emu_accel`. - Fixed the issues with installing apps to the emulator. ### Changes to Timeline: _No changes_ ### Changes for Examples: _No changes_ ### Changes for Documentation: _No changes_ --- # Pebble SDK 3.0-dp7 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp7/ _Release Date: April 9 2015_ This is the seventh Developer Preview release of Pebble SDK 3.0. ## Detailed List of Changes: ### Changes for Firmware: _No changes_ ### Changes to SDK: - Added [`graphics_draw_rotated_bitmap`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_rotated_bitmap "graphics\_draw\_rotated\_bitmap") for drawing rotated bitmaps to a GContext. - Added new [`Draw Commands`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands") which allow for doing vector-like graphics and even animating them. - Added [`action_bar_layer_set_icon_press_animation`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_press_animation "action\_bar\_layer\_set\_icon\_press\_animation") for setting the direction of the animation when selecting actions in the [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer"). - Updated [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") with new methods and callbacks to support color highlighting. ### Changes for SDK Tools: - You are no longer required to log in to the Pebble tool. If you want to use the Pebble timeline in the emulator you will still need to log in. ### Changes to Timeline: - Added `foregroundColor`, `backgroundColor`, `headings`, and `paragraphs` as new fields on the Layout object for Pins. Check out the [Pin Structure guide](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) for more details. ### Changes for Examples: - Created [cards-example](https://github.com/pebble-examples/cards-example) to demonstrate the new [Pebble Draw Commands](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands"). ### Changes for Documentation: _No changes_ --- # Pebble SDK 3.0-dp6 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp6/ _Release Date: April 3 2015_ This is the sixth Developer Preview release of Pebble SDK 3.0. ## Detailed List of Changes: ### Changes for Firmware: - The action bar has been updated for 3.0. It is now 30px wide and has new animations when the buttons are selected. - The antialiasing added in 3.0-dp5 has been improved. ### Changes to SDK: - [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") uses the new 3.0 action bar, which provides one new API function: [`action_bar_layer_set_icon_animated`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_animated "action\_bar\_layer\_set\_icon\_animated"). ### Changes for SDK Tools: _No changes_ ### Changes to Timeline: - `createMessage` and `updateMessage` have been renamed to `createNotification` and `updateNotification`. The [Node.js pebble-api](https://www.npmjs.org/package/pebble-api) client has been updated to match. ### Changes for Examples: _No changes_ ### Changes for Documentation: _No changes_ --- # Pebble SDK 3.0-dp5 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp5/ _Release Date: March 27 2015_ This is the fifth Developer Preview release of Pebble SDK 3.0. The biggest new features with this release are [antialiasing and stroke width](https://developer.repebble.com/guides/graphics-and-animations/drawing-primitives-images-and-text.md). ## Detailed List of Changes: ### Changes for Firmware: - Antialiasing is enabled by default for all apps built with SDK 3.0 or higher. ### Changes to SDK: - Added [`graphics_context_set_antialiased`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_antialiased "graphics\_context\_set\_antialiased") to toggle adding antialiasing to the graphics\_draw\_\* functions. See our [Drawing Graphics](https://developer.repebble.com/guides/graphics-and-animations/drawing-primitives-images-and-text.md) guide for more details. - Added [`graphics_context_set_stroke_width`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_stroke_width "graphics\_context\_set\_stroke\_width") to set the stroke width for drawing routines. See our [Drawing Graphics](https://developer.repebble.com/guides/graphics-and-animations/drawing-primitives-images-and-text.md) guide for more details. ### Changes for SDK Tools: - Fixed bug where projects with png-trans resources would cause build failures. ### Changes for Examples: - Added [ks-clock-face](https://github.com/pebble-examples/ks-clock-face) example to demonstrate the new antialiasing and stroke width APIs. ### Changes for Documentation: - Documented the [new pebble commands](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) for working with the emulator, and improved the rest of the tool documentation. --- # Pebble SDK 3.0-dp4 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp4/ _Release Date: March 19 2015_ This is the fourth Developer Preview release of Pebble SDK 3.0. ## Detailed List of Changes: ### Changes for Firmware: - Added basic timeline interface for testing and development purposes. **This is not the finished timeline UI**. - Pressing the up/down button from the main emulator screen will now open the timeline. - Made the generic, sports, weather and calendar layout available to timeline pins (see the [timeline guides](https://developer.repebble.com/guides/pebble-timeline.md) for more information). ### Changes to SDK: - Fixed [`property_animation_clone()`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_clone "property\_animation\_clone") compatibility macro (for Aplite binaries). - Added [`launch_get_args()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_get_args "launch\_get\_args") for getting the `launchCode` attribute from an [`openWatchApp`](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md#pin-actions) timeline pin action. ### Changes for SDK Tools: - Emulator now has the proper timezone set automatically (in firmware and in JavaScript). - Added the `insert-pin` and `delete-pin` commands to the emulator to interact with the timeline locally. - Added the `pebble login` command to connect your emulator to your Pebble account. _This is required for the timeline to work_. - Fix a bug where the build would fail if the project contains a space in the name. - The [`targetPlatforms` attribute is now supported on resources](https://developer.repebble.com/guides/app-resources/platform-specific.md). You can use it to specify that a resource should only be available on one platform. #### Known Issues The timeline will not work if you have Python 2.7.8+. Please use Python 2.7.6 if you want to work with the new timeline APIs. ### Changes for Examples: Examples have been removed from the SDK download, and our old examples repository has been deprecated. You can now find all our examples on [pebble-examples on GitHub](https://github.com/pebble-examples/). ### Changes for Documentation: _No changes_ --- # Pebble SDK 3.0-dp3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp3/ _Release Date: March 13 2015_ This is the third Developer Preview release of Pebble SDK 3.0. We have slightly modified the build process in this release to separate out logs from Aplite and Basalt when building your app. In order to take advantage of this change, you will need to update your wscript. The easiest way to do is to create a new project and copy the wscript. ## Detailed List of Changes: ### Changes for Firmware: - Multiple stability improvements. ### Changes for SDK Tools: - Separated Aplite, Basalt and bundling log output when building an app. - Added new [`hiddenApp`](https://developer.repebble.com/guides/tools-and-resources/app-metadata.md) property to appinfo.json. ### Changes for Examples: Updated examples with new wscript. ### Changes for Documentation: - Multiple improvements to various 3.0 documentation, including fixing broken links and undocumented function parameters. --- # Pebble SDK 3.0-dp2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp2/ _Release Date: March 5 2015_ This is the second Developer Preview release of Pebble SDK 3.0. We have updated the Aplite SDK to include some macros that make developing apps for both platforms easier. For example, you can now use `GColorEq` on both Aplite and Basalt and the SDK will take care of the platform differences. ## Detailed List of Changes: ### Changes for Firmware: Multiple stability improvements. ### Changes for SDK Tools: - Running the Pebble emulator on Mac OS X will no longer use up 100% CPU. - Apps built with 3.0-dp2 will install correctly on iOS - Fixed `png2pblpng` for case of 1 color causing bitdepth of 0. ### Changes for Examples: _No changes_ ### Changes for Documentation: _No changes_ --- # Pebble SDK 3.0-dp1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/3.0-dp1/ _Release Date: February 26 2015_ This is the first Developer Preview release of the brand new Pebble SDK 3.0. We will not be providing a comprehensive changelog for this release, but you can take a look at our guide to [What's New in SDK 3.0](https://developer.repebble.com/sdk/whats-new/) and our 3.0 migration guide. --- # Pebble SDK 2.9 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.9/ _Release Date: February 10 2015_ This release introduces actionable notifications for Android and minor stability improvements. ## Detailed List of Changes: ### Changes for Firmware: - Add support for [Android actionable notifications](https://developer.repebble.com/blog/2014/12/19/Leverage-Android-Actionable-Notifications.md) - Fix bug that caused crashes when [`mktime()`](https://developer.repebble.com/docs/c/Standard_C/Time.md#mktime "mktime") was used - Fix behavior of [`window_stack_pop_all`](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_pop_all "window\_stack\_pop\_all") so that only the last window is animated - Compiler will now show an error when the resources limit is reached - Improve the stability of [`Worker`](https://developer.repebble.com/docs/c/Worker.md "Worker")on launch - Fix bug where a [`Worker`](https://developer.repebble.com/docs/c/Worker.md "Worker") selected from the Activity menus would not be set to default - Fix bug where a [`Worker`](https://developer.repebble.com/docs/c/Worker.md "Worker") launched by a new app would not be set to default after the default worker was deleted - Fix an issue that caused [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") to report sends as failed when sending/recieving a high volume of messages - Notification date format is standardized: "Wednesday 11, February" -\> "Wednesday, February 11" ### Changes for SDK Tools: No changes ### Changes for Examples: - Update openweather apis used in example apps. ### Changes for Documentation: No changes --- # Pebble SDK 2.8.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.8.1/ _Release Date: December 9 2014_ This release fixes a number of bugs and improves BLE pairing on iOS. See [FW 2.8 Changelog](https://developer.repebble.com/sdk/changelogs/2.8.md) for new features and major fixes. ## Detailed List of Changes: ### Changes for Firmware: - Fix bug that would cause the watch to crash when [`accel_data_service_unsubscribe`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_data_service_unsubscribe "accel\_data\_service\_unsubscribe") was called in a `window_unload` handler - Revert error return values from [`resource_load_byte_range`](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_load_byte_range "resource\_load\_byte\_range") to pre-2.8 behavior - Speed up BLE pairing on iOS - Fix a bug that would cause an app to be built incorrectly if the first resource in appinfo.json was declared twice. - Reduce stack usage of resource handling to prevent stack overflows introduced in 2.8 - Fix several strings in non-English languages - Fix bug in [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") that would cause the watch to crash ### Changes for SDK Tools: - Fix an issue where the SDK failed to build apps with non-ascii characters in the name. - Include locale.h in pebble.h ### Changes for Examples: No changes ### Changes for Documentation: No changes --- # Pebble SDK 2.8 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.8/ _Release Date: November 20 2014_ This release changes the rendering behaviour of custom fonts in apps compiled with SDK 2.8. The change improves the visual appearance of fonts, but also causes them to be slightly larger. If you rebuild with SDK 2.8 and text no longer fits, you can revert to the old behaviour by setting `"compatibility": "2.7"` in the resource block for that font, like so: ``` {"type": "font","file": "fonts/something.ttf","name": "FONT\_SOMETHING\_24","compatibility": "2.7"} ``` System fonts are unaffected by this change. ## Detailed List of Changes: ### Changes for Firmware: - All system `GOTHIC` fonts are expanded to contain 351 characters - Add [`setlocale`](https://developer.repebble.com/docs/c/Standard_C/Locale.md#setlocale "setlocale") and [`i18n_get_system_locale`](https://developer.repebble.com/docs/c/Foundation/Internationalization.md#i18n_get_system_locale "i18n\_get\_system\_locale") APIs in preparation for internationalization support - Fix an issue that could cause an incorrect accelerometer sampling rate to be used - Fix an issue causing wakeup events scheduled less than thirty seconds in the future to fail - Improve the performance of very small resource reads - Fix an issue where iOS calendar alert notifications sometimes did not appear - Fix an issue sometimes causing spurious "Loading..." notifications to appear on iOS - Improve behaviour when trying to boot with a critically low battery ### Changes for SDK Tools: - Improve font rendering for custom fonts when compiling with SDK 2.8 - This can change the font metrics. If the font no longer fits, add the flag `"compatibility": "2.7"` to the resource entry for that font. ### Changes for Examples: No changes ### Changes for Documentation: - Fix explanation of the timezone of timestamps passed to [`wakeup_schedule`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_schedule "wakeup\_schedule") --- # Pebble SDK 2.7 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.7/ _Release Date: October 16 2014_ ## Detailed List of Changes: ### Changes for Firmware: - Add [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup") API - Add [`launch_reason`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_reason "launch\_reason") API - Fix a bug that caused [`watch_info_get_color`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#watch_info_get_color "watch\_info\_get\_color") to crash the app when used - Add [`clock_to_timestamp`](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_to_timestamp "clock\_to\_timestamp") API - Add [`clock_is_timezone_set`](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_is_timezone_set "clock\_is\_timezone\_set") API. In firmware 2.7, this function will always return false as timezone support is not yet implemented - Improve Bluetooth reliability - Fix bug showing 0% battery warning ### Changes for SDK Tools: No changes ### Changes for Examples: - Add [wakeup example](https://github.com/pebble-examples/feature-app-wakeup) ### Changes for Documentation: - Add [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup") API documentation - Fix bug with missing [`snprintf`](https://developer.repebble.com/docs/c/Standard_C/Format.md#snprintf "snprintf") specifier documentation --- # Pebble SDK 2.6.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.6.1/ _Release Date: October 1 2014_ > This release is a hotfix for the SDK 2.6 release ### Changes for SDK Tools: - Fix bug preventing use of `pebble analyze-size` - Fix bug that caused compile errors with the use of custom fonts * * * ### Pebble SDK 2.6 Release Summary ([full changelog](https://developer.repebble.com/sdk/changelogs/2.6.md)) ##### Major Changes: - Add support for [background apps](https://developer.repebble.com/guides/events-and-services/background-worker.md) with [`AppWorker`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md "AppWorker") - Add [`graphics_capture_frame_buffer`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_capture_frame_buffer "graphics\_capture\_frame\_buffer"), [`graphics_release_frame_buffer`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_release_frame_buffer "graphics\_release\_frame\_buffer"), [`graphics_frame_buffer_is_captured`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_frame_buffer_is_captured "graphics\_frame\_buffer\_is\_captured") APIs to expose framebuffer - Add [`WatchInfo`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md "WatchInfo") APIs to expose watch color, watch model, and firmware version - Add quick launch support - Bring back select-button-to-dismiss-notification on Android & iOS \< 8 - Add --worker option to `pebble new-project` to create file structure for apps with background workers - Add background worker [example](https://github.com/pebble-examples/feature-background-counter) --- # Pebble SDK 2.6 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.6/ _Release Date: September 30 2014_ > The symbols for `NUM_ANIMATION_CURVE` and `AnimationTimingFunction` have been removed in SDK 2.6. They were exposed in pebble.h in a previous release, but were not documented and are not used for [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") or [`PropertyAnimation`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md "PropertyAnimation") APIs. ## Detailed List of Changes: ### Changes for Firmware: - Add support for [background apps](https://developer.repebble.com/guides/events-and-services/background-worker.md) with [`AppWorker`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md "AppWorker") \> NOTE: The Background Worker API is not intended to be used as a wakeup mechanism for timer-based events or activities. SDK 2.7 will include a new Wakeup API that will allow you to set a timer that automatically launches your app in the foreground. Please do not use the Background Worker API to set such wakeups. - Improve bluetooth connection service by only reporting disconnections of a certain length in time - Add [`graphics_capture_frame_buffer`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_capture_frame_buffer "graphics\_capture\_frame\_buffer"), [`graphics_release_frame_buffer`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_release_frame_buffer "graphics\_release\_frame\_buffer"), [`graphics_frame_buffer_is_captured`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_frame_buffer_is_captured "graphics\_frame\_buffer\_is\_captured") APIs to expose framebuffer - Add [`WatchInfo`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md "WatchInfo") APIs to expose watch color, watch model, and firmware version - Fix bug where reading an existing key from persistent storage would fail - Fixed Sports API bug causing menu item to not always appear in app launcher - Fix bug with PebbleKit iOS and AppMessage timeouts - Add quick launch support - Bring back select-button-to-dismiss-notification on Android & iOS \< 8 - Re-enable vibration when done charging - Improve battery life ### Changes for SDK Tools: - Add a --generate command line option to the coredump command - Add --worker option to `pebble new-project` to create file structure for apps with background workers ### Changes for Examples: - Add background worker [example](https://github.com/pebble-examples/feature-background-counter) ### Changes for Documentation: - Add [AppWorker Guide](https://developer.repebble.com/guides/events-and-services/background-worker.md) - Add documentation for [`Worker`](https://developer.repebble.com/docs/c/Worker.md "Worker"), [`AppWorker`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md "AppWorker"), [`WatchInfo`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md "WatchInfo") --- # Pebble SDK 2.5 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.5/ _Release Date: September 18 2014_ > If you are upgrading from a previous version of the SDK, you will need to run the `pebble clean` command before using the SDK 2.5 with your project. ## Major Changes - FW 2.5 includes an optimized version of [`snprintf`](https://developer.repebble.com/docs/c/Standard_C/Format.md#snprintf "snprintf") (and related functions like [`APP_LOG`](https://developer.repebble.com/docs/c/Foundation/Logging.md#app_log "app\_log"), etc) that does not support some length format specifiers previously supported (%hh, %ll, %j, %z, %t). The list of supported specifiers has been updated in the [`snprintf`](https://developer.repebble.com/docs/c/Standard_C/Format.md#snprintf "snprintf") documentation. For those of you that use these previously-supported specifiers, please do not hesitate to [contact us](https://developer.repebble.com/contact.md) and we'd be happy to assist you with updating your code. - Added [compass](https://developer.repebble.com/guides/events-and-services/compass.md) support. - Enforced versionLabel formatting in appinfo.json in preparation for app auto updates. - Added support for Pebble app relaunch on iOS when a Pebble watch is in proximity. - Added notification dismissal support on iOS8. - Added emoji support to Pebble notifications and system fonts. ## Detailed List of Changes: ### Changes for Firmware: - Added functions [`heap_bytes_free`](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md#heap_bytes_free "heap\_bytes\_free") and [`heap_bytes_used`](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md#heap_bytes_used "heap\_bytes\_used") to view current heap memory usage. - Added support for [`uuid_equal`](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#uuid_equal "uuid\_equal") and [`uuid_to_string`](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#uuid_to_string "uuid\_to\_string"). - Added function [`accel_raw_data_service_subscribe`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_raw_data_service_subscribe "accel\_raw\_data\_service\_subscribe") to get accelerometer data with a single timestamp for all samples (significantly reduces memory usage for apps that do not depend on timestamps). - Added [compass](https://developer.repebble.com/guides/events-and-services/compass.md) support. - Added emoji support to Pebble notifications and system fonts `GOTHIC_24_BOLD`, `GOTHIC_18` and `GOTHIC_18_BOLD`. - Fixed a bug that would cause a crash if a screen shot was taken while one was already in progress. - Fixed an issue where Pebble APIs would use non-reentrant versions of standard C functions causing unexpected changes to return values. - Fixed a bug with accel\_service that could result in memory being freed twice. - Fixed a bug where Golf API would show stale information on disconnect. - Fixed a bug that prevented calling [`menu_layer_set_selected_index`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_selected_index "menu\_layer\_set\_selected\_index") before [`menu_layer_set_callbacks`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks "menu\_layer\_set\_callbacks"). - Fixed a bug which would sometimes cause the command line logging tool to crash when a watchapp crashed. - Fixed a bug that would cause the sample rate of the accelerometer to be reset when subscribing. - Added support for Pebble app relaunch on iOS when a Pebble watch is in proximity. - Added support for notification dismissal on iOS8. - Fixed numerous bluetooth reliability & connection issues. - Fixed a reset and other various bugs related to Data Logging. - Fixed a bug that allowed backing out of FW update screen. - Fixed a bug that would cause animations between windows to be slow. - Fixed a bug where the Date UI would allow selection of invalid dates. - Fixed a bug which would prevent the down button from scrolling through notification history. - Fixed a bug with AVRCP that could lead to a crash. - Set backlight to stay on during alarm ringing. - Changed the default backlight setting to AUTO. - Fixed a bug which would allow developers to ask for more than 25 accel samples per update. - Added check for NULL parameter in [`gpath_draw_filled`](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_filled "gpath\_draw\_filled"). ### Changes for PebbleKit iOS: PebbleKit iOS has been removed from the SDK download. Please find the latest PebbleKit iOS on [GitHub](https://github.com/pebble/pebble-ios-sdk) or on [CocoaPods](http://cocoapods.org/) under 'PebbleKit'. ### Changes for PebbleKit Android: PebbleKit Android has been removed from the SDK download. Please find the latest PebbleKit Android on [GitHub](https://github.com/pebble/pebble-android-sdk). ### Changes for SDK Tools: - Enforced versionLabel formatting in appinfo.json in preparation for app auto updates. ### Changes for Examples: - Added [compass example](https://github.com/pebble-examples/feature-compass) application. ### Changes for Documentation: - Added [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService") API document. - Added missing [`calloc`](https://developer.repebble.com/docs/c/Standard_C/Memory.md#calloc "calloc") and [`realloc`](https://developer.repebble.com/docs/c/Standard_C/Memory.md#realloc "realloc") documentation. - Improved [`tick_timer_service_subscribe`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#tick_timer_service_subscribe "tick\_timer\_service\_subscribe") documentation. - Added missing [`RotBitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md "RotBitmapLayer") documentation. - Corrected [`window_single_click_subscribe`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe "window\_single\_click\_subscribe") API entry. - Corrected time\_t time() function to specify that epoch adjusts for timezone and DST. - Fixed typo in the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") documentation. - Improved [`gbitmap_create_with_data`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_with_data "gbitmap\_create\_with\_data") documentation. - Fixed typo in documentation for [`resource_get_handle`](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_get_handle "resource\_get\_handle"). --- # Pebble SDK 2.4.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.4.1/ _Release Date: August 12 2014_ ## Detailed List of Changes: ### Changes for Firmware: - Fix a compilation problem that caused firmware 2.4 to reduce the amount of memory available to apps ### Changes for PebbleKit iOS: No changes ### Changes for PebbleKit Android: No changes ### Changes for SDK Tools: No changes ### Changes for Examples: No changes ### Changes for Documentation: No changes --- # Pebble SDK 2.4 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.4/ _Release Date: August 11 2014_ ## Detailed List of Changes: ### Changes for Firmware: - Fix a potential crash when using scroll layers or animations - Added support for realloc - Added a gbitmap\_create\_blank function to create empty bitmaps of a fixed size - Added number\_window\_get\_window() - Fixed a crash with atan2\_lookup when high input values were used - Fixed a bug where TupletInteger could not be used with unsigned integers - Fixed several bluetooth reliability issues - Fixed a case where the "Setup notifications" banner would erroneously show in the iOS Pebble app - Fixed a bug with the music app where media playing faster than real time could not be paused - Fixed a bug where the notifications view could show a rapidly increasing counter for number of notifications when first displayed - Fixed a bug where switching watchfaces could cause the same watchface to be relaunched ### Changes for PebbleKit iOS: No changes ### Changes for PebbleKit Android: No changes ### Changes for SDK Tools: No changes ### Changes for Examples: No changes ### Changes for Documentation: - Improved documentation around click handling --- # Pebble SDK 2.3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.3/ _Release Date: June 30 2014_ ## Detailed List of Changes: ### Changes for Firmware: - Don't generate multiple single click events on release if a repeating click handler is also used - Fixed a small memory leak when destroying number\_layer objects - Fixed a menu\_layer display bug when header height is set to 0 - Allow app developers to supply their own ldscript - Give a better error message when an unsupported libc function is used - \*\_destroy functions now correctly do nothing when called with NULL pointers - Fixed some BT LE connectivity issues - Fixed a crash when we ran out of persist space - Fixed a crash on reconnect when a user had a lot of pending iOS notifications - Fixed an issue where the watch would continue to vibrate after a call is ended - Fixed a display issue in Bluetooth settings when the status bar incorrectly says "Now Discoverable" in airplane mode - Fixed a display issue with the notification font settings - Fixed a display issue with the music app showing stale information when bluetooth is disconnected. - Added the ability to skip to the next and previous notification by double clicking the up and down buttons - Disabled the use of the back button for the Bluetooth pairing screen and the Alarm screen - Show a status bar icon when notifications are set to "Phone Calls Only" ### Changes for PebbleKit iOS: - Removed Bluetooth LE code from PebbleKit - Improvements to data logging to help troubleshoot issues - Removed PBWatch+PhoneVersion and +Polling - Made PBWatch+Version report the correct version - Fixed a crash when calling PBNumber description - Changed imports from \ to "HeaderName.h" format - Fixed on rare race-condition when sending data between phone and watch - Made PebbleKit.podspec pass most-recent CocoaPod linter - Prefixed internally used logging classes to fix conflict when using CocoaLumberjack in your app - Made existing logging more descriptive ### Changes for PebbleKit Android: No changes ### Changes for SDK Tools: No changes ### Changes for Examples: No changes ### Changes for Documentation: - Added documentation for the calloc libc function - Documented that text drawing functions use UTF-8 and will return errors on invalid input --- # Pebble SDK 2.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.2/ _Release Date: June 4 2014_ ## Detailed List of Changes: ### Changes for Firmware: - Music app redesign to fix some layout issues & add progress bar - Fix persist reads returning too little data if previously partially read - Additional stability improvements - Alarm now vibrates for 10 min instead of 1 min - Launcher menu is now re-orderable. Hold the select button to enter reorder mode - Volume control in the music app. Hold the select button in the music app to enter volume control mode ### Changes for PebbleKit iOS: - Removed PBWatch+PhoneVersion (moved to PebblePrivateKit) - Make PBWatch+Version report the correct version - Fixed a crash when calling PBNumber description ### Changes for PebbleKit Android: No changes ### Changes for SDK Tools: No changes ### Changes for Examples: No changes ### Changes for Documentation: No changes --- # Pebble SDK 2.1.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.1.1/ _Release Date: May 8 2014_ ## What has changed since 2.1 This release fixes a bug which caused the `pebble` tool to throw an exception when a Pebble app crashed. This is the only fix and we are not releasing a firmware 2.1.1, only the SDK is updated. ## What has changed since SDK 2.0.2 Overview: - Pebble dynamic memory allocation has been improved and will now detect when you try to free() memory twice. - With Pebble 2.1 your application will be killed and a message is shown in the console so you can detect and fix this problem, instead of potentially causing a memory corruption issue. - IMPORTANT: You will need to update your Pebble to run apps built with the 2.1 SDK. Applications compiled with the SDK 2.1 will not appear in the menu and will not run on Pebble firmware 2.0. ## Detailed List of Changes: ### Changes for Firmware: - Fixed crash caused by calling number\_window\_set\_label - Fixed white line at the bottom of MenuLayer when last row is selected - Fixed an issue where the watch would get into a reset loop after boot - Fixed issue that sometimes caused persistent storage values to not persist - Fixed issue where caller ID shows info from the previous call - Fixed caller ID sometimes not displaying on outgoing calls - Pebble dynamic memory allocation has been improved. Your application will now be killed when you try to free() memory twice - Apps can no longer crash the watch on app exit - Bluetooth reconnection is more reliable - Battery monitor is more consistent - Multiple power reduction improvements - Documentation improvements - Clip text instead of truncating when vertical space is inadequate - Notifications can be cleared via the Notification section in the Settings menu ### Changes for PebbleKit iOS: - Some improvements to datalogging to help troubleshoot issues ### Changes for PebbleKit Android: - No changes ### Changes for SDK Tools: - Allow firmware bundles to be installed with the install command - Allow SDK location to be overridden by the `PEBBLE_SDK_PATH` environment variable - Replaced PIL with pypng for taking screenshots - Fixed extra row always being added to screenshots ### Changes for Examples: - Removed ToDoList demo from SDK examples ### Changes for Documentation: - Various documentation fixes and improvements --- # Pebble SDK 2.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.1/ _Release Date: May 6 2014_ ## What has changed since SDK 2.0.2 Overview: - Pebble dynamic memory allocation has been improved and will now detect when you try to free() memory twice. - With Pebble 2.1 your application will be killed and a message is shown in the console so you can detect and fix this problem, instead of potentially causing a memory corruption issue. - IMPORTANT: You will need to update your Pebble to run apps built with the 2.1 SDK. Applications compiled with the SDK 2.1 will not appear in the menu and will not run on Pebble firmware 2.0. ## Detailed List of Changes: ### Changes for Firmware: - Fixed crash caused by calling number\_window\_set\_label - Fixed white line at the bottom of MenuLayer when last row is selected - Fixed an issue where the watch would get into a reset loop after boot - Fixed issue that sometimes caused persistent storage values to not persist - Fixed issue where caller ID shows info from the previous call - Fixed caller ID sometimes not displaying on outgoing calls - Pebble dynamic memory allocation has been improved. Your application will now be killed when you try to free() memory twice - Apps can no longer crash the watch on app exit - Bluetooth reconnection is more reliable - Battery monitor is more consistent - Multiple power reduction improvements - Documentation improvements - Clip text instead of truncating when vertical space is inadequate - Notifications can be cleared via the Notification section in the Settings menu ### Changes for PebbleKit iOS: - Some improvements to datalogging to help troubleshoot issues ### Changes for PebbleKit Android: - No changes ### Changes for SDK Tools: - Allow firmware bundles to be installed with the install command - Allow SDK location to be overridden by the `PEBBLE_SDK_PATH` environment variable - Replaced PIL with pypng for taking screenshots - Fixed extra row always being added to screenshots ### Changes for Examples: - Removed ToDoList demo from SDK examples ### Changes for Documentation: - Various documentation fixes and improvements --- # Pebble SDK 2.0.2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0.2/ _Release Date: March 18 2014_ This is another minor update to the Pebble SDK and the Pebble firmware. ## What has changed since 2.0.1 Overview: - Fixes issue that prevented some users from being able to upgrade to 2.0. - Support for XCode 5.1 - Removed Pillow as dependency for the SDK ## Known bugs and issues None. ## Detailed list of changes ### Changes for Firmware: - Fix a bug that could prevent installation fo the firmware ### Changes for PebbleKit iOS: - No changes. ### Changes for PebbleKit Android: - No changes. ### Changes for SDK Tools: - LibPebble upgrade to remove PIL dependency - replaced PIL with pypng for taking screenshots ### Changes for Examples: - Update the todolist example to use graphics\_text\_layout\_get\_content\_size instead of graphics\_text\_layout\_get\_max\_used\_size - Port improvements to simplicity from firmware to examples - Update quotes app for ready event ### Changes for Documentation: - Fixed error in API docs for accel - Fix javascript close URL in the javascript doc --- # Pebble SDK 2.0.1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0.1/ _Release Date: February 20 2014_ This is a minor update to the Pebble SDK and the Pebble firmware. ## What has changed since 2.0.0 Overview: - We have re-enabled data logging in the Pebble firmware ## Known bugs and issues - PebbleKit iOS 2.0.0 ## Detailed list of changes ### Changes for Firmware: - We have re-enabled the data logging APIs. ### Changes for PebbleKit iOS: - No changes. ### Changes for PebbleKit Android: - No changes. ### Changes for SDK Tools: - No changes. ### Changes for Examples: - Reduce the accel discs step time ### Changes for Documentation: - Updated documentation on Android intents - Updated documentation on AppMessage, AppSync, Dictionary, Typlets --- # Pebble SDK 2.0.0 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0.0/ _Release Date: February 3 2014_ This is the first public release of Pebble SDK 2.0 and Pebble firmware 2.0. ## What has changed since BETA7 Overview: - We have fixed various crashes in the firmware (this was pre-released as 2.0-RC, 2.0-RC2 and 2.0-RC3) - We have restored support for direct Bluetooth connection from the computer to the pebble in the `pebble` tool - PebbleKit iOS now includes armv7s, arm64 and x86\_64 libraries - There is a known bug in PebbleKit iOS 2.0.0 that can cause your application to crash when it is in the background. Please do not use this version to submit an application to Apple. ## Known bugs and issues - DataLogging disabled - PebbleKit iOS 2.0.0 ### Changes for Firmware: Changes since 2.0-RC3: - fix a deadlock when sending datalogging information - remove the "Your Pebble has reset" message The changes between 2.0-BETA7 and 2.0-RC3 were: - fixes a number of crashes - app no longer gets killed when it cancels an invalid timer - removes “persist\_raw -9” message - low battery message always uses the right icon - fixes crash on watch shutdown - fixes crash when using accel ### Changes for PebbleKit iOS: - Updated our version of CocoaLumberJack to fix a crash that could happen when logging in the background - Updated the build script to actually produce armv7s, arm64 and x86\_64 dynamic libraries - Improve the datalogging protocol (between PebbleApp and PebbleKit) to be more efficient ### Changes for PebbleKit Android: No changes. ### Changes for SDK Tools: - We have restored support for direct Bluetooth connection from the computer to the pebble in the `pebble` tool - Better handling of timeout errors with the websockets ### Changes for Examples: No changes. ### Changes for Documentation: - Add parameter `did_vibrate` to AccelData and explanation. - Add parameter `timestamp` to AccelData and explanation. --- # Pebble SDK 2.0 BETA7 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA7/ _Release Date: January 23 2014_ Pebble SDK 2.0 is still in BETA and is **recommended only** for developers working on new applications for the upcoming Pebble appstore. ## Update Jan 31st: 2.0 Release Candidate 3 We fixed two more crashes. iOS user will automatically get the update. Android users can download it from this site. Not sending an email to everyone this time because it really is a small changes and we want to spare your inbox before the week-end. ## Update Jan 30th: 2.0 Release Candidate 2 On January 30th, we released a 2.0 Release Candidate version of the firmware with the following changes: - fixes a number of crashes - app no longer gets killed when it cancels an invalid timer - removes “persist\_raw -9” message - low battery message always uses the right icon - fixes crash on watch shutdown - fixes crash when using accel ## Updated Jan 29th: 2.0 Release Candidate On January 29th, we released a 2.0 Release Candidate version of the firmware with the following changes: - Fixed numerous crashes - %Z flag passed to strftime no longer crashes the watch - Fixed iOS connected but not receiving anything issue - Firmware will now delete all data logging data on factory reset - Rate limit logging to prevent apps from crashing app with logging loops - Fixed issues were buttons become unresponsive - Fixed gpath getting clipped in some cases - Fixed accel lockup issue - Fixed accel not using the right sampling rate - Added low battery warning - Cancel snooze timer when alarm is deleted Please continue using BETA7 versions of the SDK and mobile applications. ## What has changed since BETA6 Overview: - More random crashes fixed in the firmware - Seriously improved datalogging on iOS (and some bugfixes on Android) - Fixed the URL scheme to install Pebble applications. It did not work in Beta6. - Added support for `getAccountToken()` in PebbleKit JS (iOS only at the moment) - iOS application and PebbleKit iOS are now 64 bits compatible - iOS application does not crash on iPhone 4 anymore - Some breaking changes in PebbleKit iOS: We cannot use NSNumber categories in 64 bit because their size is unknown. We added a new PBNumber class. This class is returned if you use the NSNumber Pebble category. ### Known Problems and limitations - Android does not include the Pebble appstore yet - PebbleKit iOS apps may see error messages about parsing firmware in their logs. This will be removed soon and does not impact anything in PebbleKit iOS. ### Changes for Firmware: - fix bugs with modal windows over fullscreen apps - fix bugs where action bar buttons could get "stuck" - reduced the power used by Pebble Steel LEDs - fix some data logging corruption issues on Pebble - fix a bug where the time of a notification would not be displayed properly - adjusted the battery charged thresholds so that Pebble Steel turns green when apps show 100% - fix a bug where you could get 110% battery - fix a bug where datalogging session could be incompletely initialized when pushed - fix a bug that could happen when looking for notifications - fix a bug where some original Pebbles (ev2\_4) would never hit 100% battery - fix infinite loop if you push a modal while one is closing - fix some button problems on Pebble Steel ### Changes for Pebble iOS App: - added support for 64bits compilation - fix a bug where 64 bit devices would not display the bluetooth accessory picker - native login / signup screen - fix some button sizes to display text properly - calculate the area of the buttons on the left menu to highlight them dynamically - data logging: do not print error messages for partially fetched data - unless we are actually done - fix some bugs around the Bluetooth accessory picker - better management of the screens stack in onboarding process. allows users to go back. - do not display icon for watchfaces in the my pebble screen - fix bug where appstore url-scheme would not work - add link to terms and conditions - deal with timeout errors while installing apps - downloading apps in the Caches directory instead of Documents since that one gets pruned automatically by the system (Fixes pebblekit#39) - only allow to start dragging the center view if you start dragging from the left edge - fix crash for iPhone 4 users - fix bug where datalogging would try to send data to the Pebble app (instead of 3rd party apps) - lazy loading the web appstore views to improve loading speed - sort apps alphabetically in the locker - memory optimization to stay in the background longer - getAccountToken() now working in JavaScript - fix a bug where the configuration view was not sometimes not dismissed - rename the "Done" button of the configuration view to "Cancel" - send empty string back to the JS if the user cancels the configuration view (as per documentation) - only show the "notifications not set up" if there's a watch connected - fix a JS bug where in some conditions the 'showConfiguration' event might be fired before the 'ready' event ### Changes for Pebble Android App: - Datalogging: if a session contains bad data, just remove it at startup ### Changes for PebbleKit iOS: - PebbleKit iOS is now 64 bits (armv7s) compatible - We cannot use NSNumber categories in 64 bit because their size is unknown. We added a new PBNumber class. This class is returned if you use the NSNumber Pebble category. - Do not start the datalogging server if appUUID is all zeros ### Changes for PebbleKit Android: No changes. ### Changes for SDK Tools: No changes. ### Changes for Examples: - Classio-battery-connection is a watchface - Onthebutton is a watchface - Rumbletime is a watchface - Fuzzy Time is a watchface - Changed example UUID's to avoid appstore collisions ### Changes for Documentation: - Add note about datalogging size --- # Pebble SDK 2.0 BETA6 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA6/ _Release Date: January 17 2014_ - Pebble SDK 2.0 is currently in BETA and intended for developers only. - Applications written for Pebble 1.x are not compatible with SDK 2.0 - If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. > **IMPORTANT NOTES FOR iOS Users** : > > - You must delete the Pebble app on your phone before installing this new version. It will now be called "Pebble Dev" and not "Pebble.". You must also re-install all of your JavaScript apps after installing this new version. > > - iPhone5S, iPad Air and Retina iPad Mini users will need to manually pair in the **Settings** of the phone. ## What has changed since BETA5 Overview: - The iOS Application distributed with BETA6 includes the new Pebble appstore - The firmware fixes a number of hard to reproduce crashes with system timers. This will fix a lot of the "Dangerously rebooting" Pebble crashes. ### Known Problems and limitations - `getAccountToken()` (in PebbleKit JS) is not working yet. It currently returns a random string. In an upcoming update (before 2.0) it will return a unique token linked to the Pebble user account. This is tied with appstore functionnalities and not available yet in this beta build. - The bugs that were reported on datalogging-iOS on BETA5 are not fixed yet in this release ### Changes for Firmware: - Rework the system timer to fix all timer related crashes - Add support for Pebble Steel LED to show charging status - Round rather than floor the battery charge percentage - Reverted timings for stm32 for 64MHz system clock based on stable 16Hz SPI clock. Fixes display flicker at 30Hz, as well as saving power at the lower system clock (80-\>64) and sleeping more often due to faster display updates. - Fix a crash when canceling the bluetooth pairing dialog - Fix a bug where pushing a window in a window\_unload callback would cause a crash - Export AccelData structure in the API doc - Vibrate when an app or watchface is installed - Fix a bug where the phone modal window would not update properly - Fix the light threshold for Pebble Steel ### Changes for Pebble iOS App: - Added the Pebble appstore - Added support for In-App Notifications - Add support for migrating 1.x apps into 2.0 apps - Fix a bug where the iOS app could crash when you switch away from a JavaScript app that has an ongoing network connection - PebbleKit JS iOS: sendAppMessage() now returns a transaction id ### Changes for Pebble Android App: - No changes. ### Changes for PebbleKit iOS: - add isNewer convenience call to PBWatch+Version - move NSJSONSerialization helper to PebbleVendor - add isEqualVersionOnly to just compare version number components, ignoring timestamp & hash ### Changes for PebbleKit Android: - No changes. ### Changes for SDK Tools: - Fix spelling in an error message (s/Insure/Ensure/) ### Changes for Examples: - No changes. ### Changes for Documentation: - Fix a 404 on the pebble tool link in the JS guide - Fix the persistence guide to reflect the new standardized parameters orders - Fix a typo in the title of the UI framework guide - Added designer resources in the UX design chapter --- # Pebble SDK 2.0 BETA5 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA5/ _Release Date: January 10 2014_ - Pebble SDK 2.0 is currently in BETA and intended for developers only. - Applications written for Pebble 1.x are not compatible with SDK 2.0 - If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. **You can start uploading your application to the Pebble appstore TODAY - [Please do so](https://developer.repebble.com/dashboard)!** ## What has changed since BETA4 Overview: - Fixed Android datalogging bugs where data would get duplicated - Merged datalogging fixes for iOS that were supposed to be in BETA4 (sorry) - Added an end of session message on Android datalogging - Fixed accelerometer bugs where the accelerometer would stop sending data - Changed the animation when switching from one watchface to the next ... - Changed the battery API to return values going up to 100% ### Known Problems and limitations - **Accelerometer locking up** : Although we have fixed several bugs around the accelerometer, we have noticed a few instance of the accelerometer locking up and the accel callback not being called anymore. If you see this happen again, please use the "Contact Support" button to send us logs. Make sure you change the subject to "Accelerometer lockup". Thank you very much! - `getAccountToken()` (in PebbleKit JS) is not working yet. It currently returns a random string. In an upcoming update (before 2.0) it will return a unique token linked to the Pebble user account. This is tied with appstore functionnalities and not available yet in this beta build. - Some crash due to internal timers and deadlock conditions are still being investigated. - This version will reset your persistent storage when you install it ### Changes for Firmware: - Added a script in the SDK to help analyze app memory usage (analyze\_static\_memory\_usage) - Changed the animation between watchfaces - Fix various composition bugs during animations - Several fix to the Pebble filesystem to fix problems occuring in persistent storage and datalogging - Add `bitmap_layer_get_bitmap()` - s/1 minutes/1 minute/ in the alarm app - Do not crash when loading a font from a NULL resource (can happen when memory is tight!) - Ignore buttons while animating from one window to another - Fix the back button in the getting started - Fix simplicity to show the time immediately - Fix sliding text to animate the time in immediately - Change simplicity to load the fonts as system fonts - Invert modal window status bar icons - Reworked `gpath_draw_filled()` to use less memory and use heap instead of stack - Improve persistent storage behaviour under tight memory - Enforce file size limits - Improve number of sectors of the filesystem - Fix a bug where in some condition going up and down after installing a watchface would not return to it - Fix a bug where `text_layer_get_content_size()` could return values that caused the text to be truncated - Do not crash in `gpath_draw_filled()` if called with 0 points - Added event service unsubscribe for app\_focus\_event (fixes a crash for Glance and Upright) - Changed the battery API to return values going up to 100% ### Changes for Pebble iOS App: - Fixes to datalogging to avoid duplicated data and iOS app getting stuck ### Changes for Pebble Android App: - Added an intent sent when a data logging session is finished - Fix a problem where JavaScript would not start on android 4.0 - Fix some bluetooth scanning bugs that could cause timeouts or pebbles not detected - Improved bluetooth pairing screens for various Android versions ### Changes for PebbleKit iOS: - Fix some threading/deadlock bugs ### Changes for PebbleKit Android: - Do not retransmit same datalogging blocks more than once - Add a callback when the datalogging session is finished ### Changes for SDK Tools: - Added command `pebble analyze-size` to dump sections and symbol sizes - Increase timeout of the wsclient (could be triggered when installing firmware) - Added `--simple` option to `pebble new-project` to create a minimalist app - Updated to websocket-client 1.12 and removed dependency to io\_sock ### Changes for Examples: - Update classio-battery-connection example to peek() the bluetooth connection status at startup ### Changes for Documentation: - Updated JS configuration example - Added link to the pebble-hacks/configurable project in the JS doc - Removed reference to the 1Hz acc sampling rate (RIP) - Added an example use of the `pebble install` command in the example page - Updated the `app_focus_subscribe` documentation in the event guide - Added a note in the datalogging guide to mention it's not a realtime system - Added doc for `only_shown_on_communication` in the anatomy of a pebble app chapter - Added that you can call `app_message_outbox_begin` in `outbox_sent` and `outbox_failed` now - Fixed formatting of the appinfo.json example in the anatomy of a pebble app chapter --- # Pebble SDK 2.0 BETA4 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA4/ _Release Date: December 23 2013_ - Pebble SDK 2.0 is currently in BETA and intended for developers only. - SDK 2.0 will be released early next year as an over-the-air update to all Pebble users. - Applications written for Pebble 1.x are not compatible with SDK 2.0 - If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. **You can start uploading your application to the Pebble appstore TODAY - [Please do so](https://developer.repebble.com/dashboard)!** ## What has changed since BETA3 Overview: - Fixed a problem where the iOS app would get killed after a few minutes in the background - Lots of Data Logging fixes on the firmware and on Android - Added timestamps on accelerometer samples - Improved error handling for PebbleKit JS apps on iOS and Android ### Firmware - Developers of apps can now register single and multi click handlers on the back button - Holding down the back button for 1.5s will force quit an existing app - Fixed bugs and optimize the filesystem: faster persist times, less problems with persistent storage, fix a bunch of rather complex problems where the recovery firmware could be damaged - Fixed scroll offset bug when displaying notifications - Dismiss missed call notfication after 180s - Fixed a bug where unicode characters were not supported in appinfo.json - Changed graphics\_text\_layout\_get\_max\_used\_size() to _not_ require a graphic context - Fixed a few more bluetooth bugs - Fixed a bug where Pebble could crash when you delete the last alarm - Fixed memory read exception that can occur when using a malloc-ed appsync buffer - Save notifications to storage during do not disturb - Document AccelAxisType in API Documentation - Fixed Music UI problems - Automatically center on screen a selected cell in a SimpleMenuLayer - Fixed bug where snprintf could crash the watch - Display an error message if a 2.0 pebble is connected to a 1.x mobile app - Fixed a bug where calling atoi() would crash an app - Many DataLogging improvements and fixes based on new unit tests - Display an alert on Pebble when we reset due to a system crash - Ignore NULL pointer passed to text\_layer\_destroy() - Limit the number of datalogging sessions to 20 - Fixed a race condition that occurs when you set the sampling rate immediately after subscribing to the accel service - Keep persistent storage intact when upgrading an application - Added timestamps on accelerometer samples and a flag indicating if the watch was vibrating at the time of the sample - Fixed a bug where psleep could crash pebble - Fixed a bug where text\_layer could add ellipsis to the wrong line ### iOS App - Fixed a bug where the iOS app would get killed in the background after just a few minutes - Show a local notification if a developer is connected but the app is about to get killed - PebbleKit JS: Fixed a bug where apps could not access localStorage with the array syntax - PebbleKit JS: Fixed a bug where a space in an URL opened with XmlHTTPRequest could crash the iOS app - PebbleKit JS: Fixed a bug where sending a byte array with 0xff in it would send 0x00 instead ### Android App - PebbleKit JS: Fixed a bug where a byte array would not be sent properly for named keys - Use new Android KitKat (4.4) APIs to do pairing on 4.4 - PebbleKit JS: Do not send ack for ack/nack messages - Fixed Android crashing with OutOfMemory error when using Data Logging - Fixed Android Data Logging of byte array that was not working ### PebbleKit iOS - Do not ack ACKs... ### PebbleKit Android - No changes ### SDK Tools - Added support to libpebble to trigger reboot to recovery firmware - Added support for computers where python2 and python3 co-exist - Fixed an exception when receiving APP\_LOG with extended characters - Fixed a bug where unicode characters were not supported in characterRegex field of `appinfo.json` - Fixed 30 second delay that can occur when building pebble apps on Ubuntu when there is no internet access - Added Pillow python dependency: needed for the screenshot functionality - Detect PIL/Pillow conflict and suggest a fix to the user ### Examples - Added a License to the examples --- # Pebble SDK 2.0 BETA3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA3/ _Release Date: December 12 2013_ - Pebble SDK 2.0 is currently in BETA and intended for developers only. - SDK 2.0 will be released later this year as an over-the-air update to all Pebble users. - Applications written for Pebble 1.x are not compatible with SDK 2.0 - If a 2.0 version of an application exists, the Pebble mobile applications will automatically install it when when a user upgrades her/his Pebble. ## What has changed since BETA2 Overview: - The Android app fixes a large number of JS-related bugs. - The Android app fixes a bug where all messages sent to android would be automatically acknowledged. Your application should acknowledge app messages. - Some new user features in the firmware: Notification settings (with Do Not Disturb), better Alarms - Lots of small UI and stability fixes in Pebble. Known problems and bugs: ### Firmware - added a Notification menu in the Settings to disable Notifications and configure a DoNotDisturb time frame - much better Alarm app with a nicer UI, snooze support, disabled alarms support - fix bugs where incoming calls could cause the vibration to stay on continuously - fix a rare condition where the accelerometer would crash if an interrupt comes too late or the accelerometer sent 0 samples - fix accelerometer behaviour when only 1 sample is requested: only send one sample (instead of 2) - fix a bug where an iOS device could disconnect just 1-2 seconds after connecting - automatically reconnect when user leaves Airplane Mode - show (in settings) that vibrations are disabled when Pebble is plugged - improved the set date/time UI to use the new DateTime UI (as in Alarms) - adjust the framebuffer offset for modal window transitions - reduced BLE connection interval - log more information when an application crashes - do not crash if an app\_message section is re-opened - display warning instead - fix a bug which caused firmware updates to fail under some conditions (mostly android) - appsync will only update a dictionary if it has enough memory to do so (instead of finding out half-way that it does not have enough memory) - always return to the launcher after an app crash (to avoid a crash loop on a watchface) - \*\_destroy() will accept NULL pointers without errors - always go back to the top of the menu when launching the menu from a watchface (to make "blind" navigation easier) - fix a bug where an actionbar button would still be "pressed" - show Bluetooth Name on getting started screen - automatically delete old apps that are not compatible with this firmware version - accelerate scrolling on the menu - use modal window icon in the status bar as long as the modal window is displayed - Export dict\_size so external developers don't have to do pointer math :) - fix a bug where scrolling in a long list of notifications could cause the display to "bounce" - fix a bug where lots of app logging messages could overflow the system task queue and kill app message - API documentation completely reviewed and updated - missed call modal views will timeout after 180s - force quit app when the back button is held for 2 seconds - menu\_cell\_basic\_draw() now automatically center the text in the cell. If you do not want a subtitle, pass NULL (otherwise, space will be reserved for the subtitle). - fixed some bluetooth settings to avoid duplicated messages (could cause screenshot to go over 100%, duplicated log entries, firmware upgrade to fail, etc) - `peek()`ing the accelerometer is not allowed anymore when you have also subscribed to receive updates - fix a bug where the accelerometer would get stuck after a few hours ### iOS App - fix a bug where datalogging could dump messages to another phone on your local network - fix a bug where datalogging would get into a deadlock - fix a bug where the developer connection would appear active but would be closed ### Android App - fix a number of cases where a JS app would not be launched - fix bug where clicking the configure icon would not open the configuration view of an app - fix a bug which caused every AppMessage sent to Android to be acknowledged by the system - Select the Google Play Music App as the default music player - fix support email to use the Pebble bluetooth name instead of the last four digits of the serial - if there is an error when uploading an app, do not dismiss the update screen right away - do not dump large logs if stats json is not found - check for firmware update when foregrounded - fix bug where a canceled app install would be reported as completed - fix bug where an install would fail silently because the resources could not be loaded - display specific error message when a user tries to install a 2.0 app on a 1.x Pebble - fix a bug where the android app would display error message "Could not update" while looking for updates in the background ### PebbleKit iOS - allow one iOS application to exchange messages with several Pebble apps (with different UUIDs) - fix a crash trying to parse invalid firmware version - add CocoaPods support (see pebblekit-ios readme for more info) - enabled "all warnings" and fixed errors ### PebbleKit Android No changes. ### SDK Tools - added support to upload any bundle (including firmware) - added test to detect missing tools - better implementation of the --debug flag - fix bug where tools would fail when installed in a folder with a space in it - fix bug where tools would fail on project with a space in the name - some 1.x to 2.x conversion bugs fixed - automatically re-enable applog when the watch reconnects ### Examples - fix crashing bugs in 91Dub --- # Pebble SDK 2.0 BETA2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA2/ _Release Date: November 14 2013_ - Pebble SDK 2.0 is currently in BETA and intended for developers only. - SDK 2.0 will be released later this year as an over-the-air update to all Pebble users. - Applications written for Pebble 1.x are not compatible with SDK 2.0 ## What has changed since BETA1 Overview: - We have included ANCS in 2.0 - iOS users will get all notifications - We have added a screenshot tool - We have increased the AppMessage buffer size for PebbleKit JS Apps - We have changed a few firmware APIs to always pass parameter in this order: (buffer, size) - We have fixed many bugs Known problems and bugs: - We are still working actively on improving datalogging on iOS and Android. If you wish to use this framework, please get in touch with us and tell us about your experience. - JavaScript apps on Android will only run if the phone is turned on and the Pebble app running (the easiest way to check this is to bring it to the foreground). This will be fixed soon. - If you downloaded the SDK before 5pm PST on 2013-11-14, your API documentation is probably broken. We have fixed this and pushed a new release without updating the version number because there are absolutely no changes (except the doc is now there ;). ### Firmware - Added support for ANCS - Fix UI bug when getting phone calls - Improved address book lookups when getting phone calls - Changed the behaviour when an app is closed from PebbleKit: return to the last running app or watchface (instead of the launcher) - Show malloc and free in the generated documentation - Fix doc for AccelAxisType - Do not animation a window disappearing if the window was pushed without animation - Add `GCornersRight` in the documentation of `GCornerMask` - Document `GTextOverflowMode` - Document the return value of the `persist_*` functions - Document `AppTimerCallback` - When exiting an app, all unload handlers will be called for loaded windows - Changed the order of parameters for `persist_read_data()`, `persist_read_string()`, `persist_write_data()`, `dict_calc_buffer_size()`, `dict_serialize_tuplets_to_buffer_with_iter()`: always ask for the pointer first and then the count or size - Fix bug where the status bar would not be displayed properly - Enabled Accelerometer high resolution output - Automatically reset the accelerometer when app exits - Removed the 1Hz accelerometer settings because it breaks the shake to backlight - Use peek() instead if you only need one sample per second. - Updated the guaranteed minimum buffer sizes for appmessage. They are in fact 124 / 636. - Fix bug where appLaunch commands would not be ACK'd - Increased AppMessage buffer sizes for JavaScript apps: they get 2k in and out. ### iOS App - Fixed several dataLogging bugs - Fixed most common crashes reported by TestFlight ### Android App - Fixed several dataLogging bugs - Fixed most common crashes reported by TestFlight ### PebbleKit iOS - DataLogging apps do not need to include an `appInfo.json` file anymore - Use `setAppUUID` to give the UUID of the app you want to talk to ### PebbleKit Android - Add `getWatchFWVersion()` to get a `FirmwareVersionInfo` object - Add `isDataLoggingSupported` ### SDK Tools - Added a `screenshot` command to the `pebble` tool - Revert the change in the tool where we would enforce a specific range of uuids - Improved error messages when the tools cannot be found - Do not truncate log messages coming from the JavaScript console - Only log app\_log (and not system log) by default. Use `--verbose` to get all the logs. ### Examples - Fix a bug in the dataspooling demo where sealions and pelicans got mixed up - Fix PebbleKit Examples for the new `setAppUUID` style - Fix examples to use the new parameter orders for `persist` functions --- # Pebble SDK 2.0 BETA1 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA1/ _Release Date: November 6 2013_ - Pebble SDK 2.0 is currently in BETA and intended for developers only. - SDK 2.0 will be released later this year as an over-the-air update to all Pebble users. - Applications written for Pebble 1.x are not compatible with SDK 2.0 - ANCS Notifications (aka BLE notifications) are not supported for iOS users in this version Updates: - 2013 11 07: Added a firmware for watch with serial number starting with a 'Q' (aka hardware 1.5) ## What has changed since BETA0 ### User Bug Fixes and Feature Enhancements - Fixed crashing bugs on the iOS app. Users should experience improved stability. - New iOS users no longer need to manage access to their address book in order to see Caller ID on their Pebble. - The iOS app does not overflow the banner bar (at the top of the screen) on iOS7 - The Pebble now can show \>80 unread notifications, up from 8 previously. - Backlight is triggered on a tap from any of the 6 axes of the watch - Android app stability has been improved - On Android, switching orientation while updating firmware does not stop the firmware update - The music app now stays open rather than switching back to the menu after 1 minute ### Known User Issues - The status indication button in the main screen sometimes repeatedly throbs green then red, repeatedly. - (iOS7 users, iPhone4S and higher) If you select "Enable Notifications" and select the Cancel button in the system alert that comes up, it can take up to 30 seconds for the iOS app to allow selection of "Enable Notifications" again. As a workaround, if you launched this screen from the Status screen, you can hit the up arrow, then the red "Not receiving notifications" button, and retry enabling notifications again. - In certain conditions if you enable and disable Airplane mode on your Pebble, you may need to restart the Pebble iOS app completely in order to re-enable notifications again - On Android, you may need to restart the Pebble app after installing a new version of a JavaScript app to ensure that your changes are successfully loaded. - On Android, use of HTML5 local storage does not guarantee data will be saved across sessions. - Duplicate APP\_LOG messages can be received while using the pebble tool; these are intermittent and developers should use timestamps to identify duplicates - If there is not enough app heap remaining, some essential functions that allocate on that heap will fail, such as system fonts or persistent storage - The iOS app can sometimes crash when opening a PBW file if it is not already running ### Developer Bug Fixes and Enhancements (Major Feature Enhancements are covered in the SDK) - Apps now only need one Pebble specific header, pebble.h - Exiting an app showing no windows will now not crash the Pebble - Pebble will not crash when cancelling an already cancelled timer - Pebble will not crash when cancelling an unregistered timer - Holding the up or down buttons now cause repeated clicks in menus - Changed the default stroke color to Black instead of White, as the default background color is White - Apps now cannot overwrite the system memory, and will be terminated if they attempt to - Int type changes on many APIs to ensure future compatability - User data can be attached to a window - The pebble tool displays an error message if you try to install an application that is not compatible with the target firmware - The menu icon resource is displayed even if it is not the first resource - Libpebble times out if no apps are installed - The valid range for UUIDs has changed - see the developer documentation - The Android app now installs bundles in Gmail attachments - System fonts now show capital W, Q and O - `pebble Install`` will now install even if the Android app is left in the “Update” screen - Apps will not crash if a text layer is not large enough to hold the requested text - `pebble install --logs` proceeds to tail logs even when install fails - Apps will not crash when popping/removing already popped/removed windows from the stack --- # Pebble SDK 2.0 BETA0 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-BETA0/ _Release Date: November 1 2013_ This version is a preview of what will be publicly released soon as a BETA. This means that it is the last time we introduce large changes to the APIs, they will be much more stable in the future. It includes some last significant changes that will impact every application. - We have changed the format of the `wscript` file. **You must update your wscript file.** The easiest way to do this is to generate a new project with `pebble new-project` and use the generated `wscript`. - Header files `pebble_os.h`, `pebble_app.h` and `pebble_fonts.h` are replaced by `pebble.h` - `click_config_provider()` signature has changed and instead of filling a struct, you call `window_*_click_subscribe`. Please refer to the Migration Guide. - On AppMessage: - We have changed the signature of most AppMessage functions. Please refer to the Migration Guide. - We have added functions to query the size of the AppMessage buffers. They still return the same value that in previous versions ... for now. - We have added a [Mobile Developer Guide](https://developer.repebble.com/guides/communication.md) covering PebbleKit iOS and Android. Please take a look at them, they should answer lots of questions. - [PebbleKit Android Documentation](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) is now available on the website and in the SDK `Documentation` folder. - We have done a lot of work on PebbleKit JavaScript: - The [documentation](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) describes the new model for loading and stopping JavaScript apps. You should take a look. - On Android only (for now) apps will automatically start when they get a message from Pebble. - On Android only (for now) you can use the gear icon to open a configuration window on the phone. - You can now call `Pebble.addEventListener` instead of `PebbleEventListener.addEventListener` - DataLogging is now supported on Android, iOS6 and iOS7 - And of course we have fixed a large quantities of bugs. This is a private release under NDA. --- # Pebble SDK 2.0 DP3 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-DP3/ _Release Date: October 21 2013_ This version brings some major improvements and a lot of bugfixes. In a nutshell, the big changes are: - PebbleKit JavaScript now supports geolocation on all platforms - Pebble supports the ANCS protocol. See details below. This is a private release under NDA. ### Pebble and ANCS Pebble has been working on integrating Bluetooth Low Energy (BLE) technology into our upcoming software releases. The initial goal for this work is to greatly enhance the notification experience between a Pebble and a BLE-capable iOS7 device (the iPhone4S and later) - this leverages the "ANCS" notification feature of iOS7. A requirement for the public release of BLE-capable Pebble SW is that it will not change the Android experience. We will work on enhancing the BLE experience specifically for Android users in future SW releases. If you wish to help Pebble test BLE and ANCS, please read this carefully, this is pre-release software and there are still areas of the experience we are actively enhancing. We greatly appreciate your help in testing these important features :-) Pebble SDK DP3 (and up) include BLEs capabilities. Download the firmware and mobile apps as instructed in the installation instructions. You do not need anything else. To configure ANCS and BLE: - If you already had email configured in the iOS Pebble app, go into the Pebble app and turn that OFF. With ANCS, email notifications will automatically mirror the notifications that show up on your phone. - The first time you set this up (after you install BLE/ANCS firmware) you will need to pair your phone with the watch to make the BTLE connection. - On the watch, go into the "Settings" view, and select "Bluetooth". - On your iOS7 iPhone go into the "Settings" app, select "Bluetooth". - You should see an entry called "Pebble-LExxxx" where xxxx is the 4 digit code that is shown at the top of the Pebble's Bluetooth screen. Select that entry, and confirm pairing. - Ensure that BOTH traditional Bluetooth and BLE are paired. You will not be able to perform all of the functions (such as handling phone calls) if the Bluetooth-Classic connection is not working. - We are actively working on enhancements to pairing, so this process will change as we near public release. Known issues: - Only pairing from iOS BT Settings works for now. In-Pebble-app pairing is still TODO. - If you have a red bar "Notifications require additional setup" in your iOS app, this will not disappear when LE is paired / ANCS is activated. You can safely ignore it. - Gmail/IMAP in Pebble app + ANCS = Duplicate emails. We recommend turning off email accounts from the Pebble iOS app. - "Forget" from the watch's BT settings menu doesn't work as expected. iDevice immediately reconnects again. - All notifications have same icon - Max message length is shorter than Bluetooth Classic. - Impact on battery life: we are actively characterizing and working on this, but it is currently less than Bluetooth-Classic only. Please report any bugs by email to: [ancsbug@getpebble.com](mailto:ancsbug@getpebble.com)! **Remember, this release is under NDA. Please do not share word of this new feature. Thanks a lot!** ### Known problems and bugs - Data Logging still does not work on iOS7 - On iOS, to try the "openURL()" function, you must first click the "Details Indicator" button on the table view that lists the JavaScript process - On Android, to upgrade an existing JavaScript app, you must first kill it in the "JS App Processes" view (look for the Skull And Bones button) - On some Android phones running 4.1, we have encountered a situation where location services were not working. This problem and the appropriate fix is described by Google [in this forum post](http://productforums.google.com/forum/#!msg/mobile/LEPcl9e3dYE/3LZEhiWACigJ). ### Pebble Firmware - Fix a bug where Pebble would keep vibrating after answering a call ### Pebble SDK - Fix a bug which caused all apps to share the same persistent storage file ### PebbleKit iOS/Android - Removed some deprecated/private APIs call from PebbleKit-iOS - Update PebbleKit-iOS project files to Xcode 5 - Fix PebbleKit Android build - moved libraries to libs/ ### PebbleKit JavaScript - Getting current location now works on iOS and Android. It is also possible to watch the current position and be notified when it changes. - Fixed a bug on iOS where sending multiple digits number would not work - Fixed a bug with the 2.1 release on Android where it would be impossible to use AppMessage (with PebbleKit JS) - Receiving byte arrays now also works on iOS - Added Pebble.getAccountToken() to get a unique token for the current user account (Note: this is not documented yet.) ### pebble tool - Do not return 0 if something bad happened - Display the footprint of the app in RAM and the available heap space ### Pebble iOS Application - Fixed a UI bug on iOS7 when deleting an app - Fixed the Developer Mode UI on iOS7 #### 2013 10 25 - 2.0-DP3.1 We have release a 3.1 update for the iOS application which should fix the most common crash for the app. ### Pebble Android application - Fixed a bug where the Android app would continuously try to connect to the Pebble even after disconnecting/unpairing - Fixed a bug where Facebook notifications would have duplicated content in the name field and the main field - Automatically start an app after installation - Fixed a bug where it would be impossible to skip the onboarding process - Fixed a bug where switching the device orientation during firmware upgrade would cause the upgrade to start again ### Pebble SDK Examples - Fixed a crash in dropzone - Improved the weatherjs example to use geolocation and display the name of the city - Added a very cool arcade game to demonstrate use of persistent storage (watchapps/pebble\_arcade) ### Documentation - Simplified installation instructions for Linux - Fixed a lot of broken links - Added a chapter on iOS whitelisting - Added a chapter on fonts - Reworked most of the developer guides --- # Pebble SDK 2.0 DP2 - Changelog Source: https://developer.repebble.com/sdk/changelogs/2.0-DP2/ _Release Date: September 24 2013_ > 2.0 DP2.1 > > We do not like making releases twice a week but we really wanted to fix the iOS/PebbleKit JS bug and so here it is. PebbleKit JS can now receive message on iOS and on Android. You only need to update your SDK for this to work. We will not release new versions of the mobile apps. > > The rest of the DP2 release notice below still applies. ### Known problems and bugs - Data Logging does not work on iOS7 - The watch can run out of memory if the applications do not release the memory properly - On iOS, PebbleKit JS can not receive app messages (fixed in the DP2.1 release) ### In a nutshell - The format of applications has changed. Every application now requires an `appinfo.json` file in its base directory. This file contains the UUID, name and resources of the application. For PebbleKit JS apps, it may also contain the keys used for app\_message communication. - The `pb-sdk` tool is gone. It is replaced by the `pebble` command line. - Instead of having the phone connect to the developer box, the developer tools will connect to the mobile application. When you turn on developer mode on your phone, it will display the IP address that you should use to connect to the phone. You can set a `PEBBLE_PHONE` environment variable to avoid retyping this all the time. - Fixed most blocking bugs reported on Developer Preview 1 (details below) - The Developer Guide has been completely rewritten and also includes a migration guide. ### Pebble Firmware - Fixed a bug where launching an application through the bluetooth protocol would cause the app to be re-launched if it was already running. - Added support for the middle button in the Golf app. This will send a message that is received by PebbleKit on iOS and Android. - Tap event is now disabled during a vibration (to avoid triggering the event) - Bumped firmware version and added tests to make sure that old apps will not run on the new firmware and vice-versa - Fixed various issue with firmware updates - AppLog does not need to be enabled manually anymore. It is automatically enabled by the `pebble` tool. ### Pebble SDK - Finalized conversion to new dynamic memory model: all \_init functions have been replaced by \_create() equivalent that return a pointer to a newly allocated and initialized structure (gbitmap\_init functions, gpath\_init, property\_animation\_init and app\_sync\_init have been updated to the new style) - Trigger a battery event when the percentage of battery charge changes (will trigger every 2%) - Data Spooling now takes a `void*` pointer (to avoid useless casting in developer code) - Data spooling session ids are now random - persist\_read\_int now returns 0 if the key does not exist (as per documentation) - Global static variables were not initialized properly - Fix a dataspooling bug where sometimes the close message did not contain the correct session id - Added a bluetooth\_connection\_service\_peek() function - Export atol/atoi functions - Export app\_comm\_get\_sniff\_interval - As a developer, I can call the atan()/atan2() function to compute an arc-tangent - Renamed DataSpooling into DataLogging - Defined a new Design Pattern to subclass layers and included an example based on the famous Progress Bar layer (`watchapps/feature_layer_data`) ### PebbleKit iOS/Android - Redesigned completely the Android API for Data Logging (ex data spooling) ### PebbleKit JavaScript - Fixed a bug where only single digit integers would be passed as integers - On Android, the apps are now available in the "Webapps" menu - On iOS and Android, applications will keep on running once they are started (but they do not start automatically yet) - On iOS, you need to tap the appname to run it. - On iOS, to allow for `openURL` to work, you need to open the javascript console first - On iOS and Android, javascript `console.log` are sent over the developer connection (available with `pebble logs`) - You can now send array of bytes by passing an array of integers to `sendAppMessage()` - The keys in the appinfo.json file is now optional and you can use integers in strings as keys - If a received message as a key that is not listed in the `appKeys` block, the integer will be converted to a string - A bug where the navigation stack could be corrupted when calling `openURL` is fixed ### pb-sdk - Renamed pb-sdk into pebble - Added a --version option - Added a command to clean current project - Added an example of using APPLOG in the default generated project - Return meaningful error codes to the shell - Fixed a bug where `pb-sdk list` would fail if no apps are installed - Added a --javascript option to `pb-sdk new-project` to create a template of JS code and automatically generate the appinfo.json - Automatically detect old projects, refuse to build and offer the option to update the project - Added `convert-project` command to update old projects to the new style (note: this will not update the source code) - Added clean error message if a resource is missing ### iOS Application - The developer preview iOS application will automatically offer new version of the beta firmware - Added support for middle button in golf app - Some iOS7 fixes - Switched to TestFlight for distribution and crash reports ### Android Application - The developer preview Android application will automatically offer new version of the beta firmware - Added support for middle button in golf app - Switched to TestFlight for distribution and crash reports ### Pebble SDK Examples - Added an example for the persistent storage APIs - Fixed all the iOS examples to build out of the box - Reworked the Ocean Data Survey example --- # Animated Images Source: https://developer.repebble.com/guides/app-resources/animated-images/ The Pebble SDK allows animated images to be played inside an app using the [`GBitmapSequence`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapSequence "GBitmapSequence") API, which takes [APNG](https://en.wikipedia.org/wiki/APNG) images as input files. APNG files are similar to well-known `.gif` files, which are not supported directly but can be converted to APNG. A similar effect can be achieved with multiple image resources, a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") and an [`AppTimer`](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimer "AppTimer"), but would require a lot more code. The [`GBitmapSequence`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapSequence "GBitmapSequence") API handles the reading, decompression, and frame duration/count automatically. ## Converting GIF to APNG A `.gif` file can be converted to the APNG `.png` format with [gif2apng](http://gif2apng.sourceforge.net/) and the `-z0` flag: ``` ./gif2apng -z0 animation.gif ``` > Note: The file extension must be `.png`, **not** `.apng`. ## Adding an APNG Include the APNG file in the `resources` array in `package.json` as a `raw` resource: ``` "resources": {"media": [{"type":"raw","name":"ANIMATION","file":"images/animation.png"}]} ``` ## Displaying APNG Frames The [`GBitmapSequence`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapSequence "GBitmapSequence") will use a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") as a container and update its contents each time a new frame is read from the APNG file. This means that the first step is to create a blank [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") to be this container. Declare file-scope variables to hold the data: ``` static GBitmapSequence \*s\_sequence;static GBitmap \*s\_bitmap; ``` Load the APNG from resources into the [`GBitmapSequence`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapSequence "GBitmapSequence") variable, and use the frame size to create the blank [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") frame container: ``` // Create sequences\_sequence = gbitmap\_sequence\_create\_with\_resource(RESOURCE\_ID\_ANIMATION);// Create blank GBitmap using APNG frame sizeGSize frame\_size = gbitmap\_sequence\_get\_bitmap\_size(s\_sequence);s\_bitmap = gbitmap\_create\_blank(frame\_size, GBitmapFormat8Bit); ``` Once the app is ready to begin playing the animated image, advance each frame using an [`AppTimer`](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimer "AppTimer") until the end of the sequence is reached. Loading the next APNG frame is handled for you and written to the container [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"). Declare a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") variable to display the current frame, and set it up as described under [_Displaying An Image_](https://developer.repebble.com/guides/app-resources/images.md#displaying-an-image). ``` static BitmapLayer \*s\_bitmap\_layer; ``` Create the callback to be used when the [`AppTimer`](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimer "AppTimer") has elapsed, and the next frame should be displayed. This will occur in a loop until there are no more frames, and [`gbitmap_sequence_update_bitmap_next_frame()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_update_bitmap_next_frame "gbitmap\_sequence\_update\_bitmap\_next\_frame") returns `false`: ``` static void timer\_handler(void \*context) {uint32\_t next\_delay;// Advance to the next APNG frame, and get the delay for this frameif(gbitmap\_sequence\_update\_bitmap\_next\_frame(s\_sequence, s\_bitmap, &next\_delay)) {// Set the new frame into the BitmapLayerbitmap\_layer\_set\_bitmap(s\_bitmap\_layer, s\_bitmap);layer\_mark\_dirty(bitmap\_layer\_get\_layer(s\_bitmap\_layer));// Timer for that frame's delayapp\_timer\_register(next\_delay, timer\_handler, NULL);}} ``` When appropriate, schedule the first frame advance with an [`AppTimer`](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimer "AppTimer"): ``` uint32\_t first\_delay\_ms = 10;// Schedule a timer to advance the first frameapp\_timer\_register(first\_delay\_ms, timer\_handler, NULL); ``` When the app exits or the resource is no longer required, destroy the [`GBitmapSequence`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapSequence "GBitmapSequence") and the container [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"): ``` gbitmap\_sequence\_destroy(s\_sequence);gbitmap\_destroy(s\_bitmap); ``` --- # App Assets Source: https://developer.repebble.com/guides/app-resources/app-assets/ This guide contains some resources available for developers to use in their apps to improve consistency, as well as for convenience. For example, most [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") implementations will require at least one of the common icons given below. ## Example PDC icon SVG Files Many of the system PDC animations are available for use in watchfaces and watchapps as part of the [`pdc-sequence`](https://github.com/pebble-examples/pdc-sequence/tree/master/resources) example project. ## Example Action Bar Icons There is a set of example icons for developers to use for common actions. Each icon is shown below as a preview, along with a short description about its suggested usage. | Preview | Description | | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/action_bar_icon_check.png) | Check mark for confirmation actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/action_bar_icon_dismiss.png) | Cross mark for dismiss, cancel, or decline actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/action_bar_icon_up.png) | Up arrow for navigating or scrolling upwards. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/action_bar_icon_down.png) | Down arrow for navigating or scrolling downwards. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/action_bar_icon_edit.png) | Pencil icon for edit actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/action_bar_icon_delete.png) | Trash can icon for delete actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/action_bar_icon_snooze.png) | Stylized 'zzz' for snooze actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/music_icon_ellipsis.png) | Ellipsis to suggest further information or actions are available. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/music_icon_play.png) | Common icon for play actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/music_icon_pause.png) | Common icon for pause actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/music_icon_skip_forward.png) | Common icon for skip forward actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/music_icon_skip_backward.png) | Common icon for skip backward actions. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/music_icon_volume_up.png) | Common icon for raising volume. | | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/icons/music_icon_volume_down.png) | Common icon for lowering volume. | --- # App Resources Source: https://developer.repebble.com/guides/app-resources/ The Pebble SDK allows apps to include extra files as app resources. These files can include images, animated images, vector images, custom fonts, and raw data files. These resources are stored in flash memory and loaded when required by the SDK. Apps that use a large number of resources should consider only keeping in memory those that are immediately required. **Notice** The maximum number of resources an app can include is **256**. In addition, the maximum size of all resources bundled into a built app is **128 kB** on the Aplite platform, and **256 kB** on the Basalt and Chalk platforms. These limits include resources used by included Pebble Packages. App resources are included in a project by being listed in the `media` property of `package.json`, and are converted into suitable firmware-compatible formats at build time. Examples of this are shown in each type of resource's respective guide. ## Contents - [**Animated Images**](https://developer.repebble.com/guides/app-resources/animated-images.md) - How to add animated image resources to a project in the APNG format, and display them in your app. - [**App Assets**](https://developer.repebble.com/guides/app-resources/app-assets.md) - A collection of assets for use as resources in Pebble apps. - [**Converting SVG to PDC**](https://developer.repebble.com/guides/app-resources/converting-svg-to-pdc.md) - How to create compatible SVG files using Inkscape and Illustrator. - [**Fonts**](https://developer.repebble.com/guides/app-resources/fonts.md) - How to use built-in system fonts, or add your own font resources to a project. - [**Images**](https://developer.repebble.com/guides/app-resources/images.md) - How to add image resources to a project and display them in your app. - [**Pebble Draw Command File Format**](https://developer.repebble.com/guides/app-resources/pdc-format.md) - The binary file format description for Pebble Draw Command Frames, Images and Sequences. - [**Platform-specific Resources**](https://developer.repebble.com/guides/app-resources/platform-specific.md) - How to include different resources for different platforms, as well as how to include a resource only on a particular platform. - [**Raw Data Files**](https://developer.repebble.com/guides/app-resources/raw-data-files.md) - How to add raw data resources to a project and read them in your app. - [**System Fonts**](https://developer.repebble.com/guides/app-resources/system-fonts.md) - A complete list of all the system fonts available for use in Pebble projects. --- # Converting SVG to PDC Source: https://developer.repebble.com/guides/app-resources/converting-svg-to-pdc/ [Pebble Draw Commands](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands") (PDC) are a powerful method of creating vector images and icons that can be transformed and manipulated at runtime. These can be used as a low-cost alternative to APNGs or bitmap sequences. Currently the only simple way to create PDC files is to use the [`svg2pdc.py`](https://github.com/pebble-examples/cards-example/blob/master/tools/svg2pdc.py) tool. However, as noted in [_Vector Animations_](https://developer.repebble.com/tutorials/advanced/vector-animations.md#creating-compatible-files) there are a some limitations to the nature of the input SVG file: > The `svg2pdc` tool currently supports SVG files that use **only** the following elements: `g`, `layer`, `path`, `rect`, `polyline`, `polygon`, `line`, `circle`. Fortunately, steps can be taken when creating SVG files in popular graphics packages to avoid these limitations and ensure the output file is compatible with `svg2pdc.py`. In this guide, we will be creating compatible PDC files using an example SVG - this [pencil icon](https://upload.wikimedia.org/wikipedia/commons/a/ac/Black_pencil.svg). ![pencil icon](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/pencil.svg) ## Using Inkscape - First, open the SVG in [Inkscape](https://inkscape.org/en/): ![inkscape-open](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/inkscape-open.png) - Resize the viewport with _File_, _Document Properties_, _Page_, _Resize Page to Drawing_: ![inkscape-resize-page](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/inkscape-resize-page.png) - Select the layer, then resize the image to fit Pebble (50 x 50 pixels in this example) with _Object_, _Transform_: ![inkscape-resize-pebble](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/inkscape-resize-pebble.png) - Now that the image has been resized, shrink the viewport again with _File_, _Document Properties_, _Page_, _Resize Page to Drawing_: - Remove groupings with _Edit_, _Select All_, then _Object_, _Ungroup_ until no groups remain: ![inkscape-ungroup](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/inkscape-ungroup.png) - Disable relative move in _Object_, _Transform_. Hit _Apply_: ![inkscape-relative](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/inkscape-relative.png) - Finally, save the image as a 'Plain SVG': ![inkscape-plain](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/inkscape-plain.png) ## Using Illustrator - First, open the SVG in Illustrator: ![illustrator-open](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/illustrator-open.png) - Resize the image to fit Pebble (50 x 50 pixels in this example) by entering in the desired values in the 'W' and 'H' fields of the _Transform_ panel: ![illustrator-resize](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/illustrator-resize.png) - Ungroup all items with _Select_, _All_, followed by _Object_, _Ungroup_ until no groups remain: ![illustrator-ungroup](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/illustrator-ungroup.png) - Shrink the image bounds with _Object_, _Artboards_, _Fit to Selected Art_: ![illustrator-fit](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/illustrator-fit.png) - Save the SVG using _File_, _Save As_ with the _SVG Tiny 1.1_ profile and 1 decimal places: ![illustrator-settings](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/illustrator-settings.png) ## Using the PDC Files Once the compatible SVG files have been created, it's time to use `svg2pdc.py` to convert into PDC resources, which will contain all the vector information needed to draw the image in the correct Pebble binary format. The command is shown below, with the Inkscape output SVG used as an example: ``` $ python svg2pdc.py pencil-inkscape.svg# Use python 2.x! ``` > If a coordinate value's precision value isn't supported, a warning will be printed and the nearest compatible value will be used instead: > > ``` > Invalid point: (9.4, 44.5). Closest supported coordinate: (9.5, 44.5) > ``` To use the PDC file in a Pebble project, read [_Drawing a PDC Image_](https://developer.repebble.com/tutorials/advanced/vector-animations.md#drawing-a-pdc-image). The result should look near-identical on Pebble: ![svg-output](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/svg-output.png) ## Example Output For reference, compatible output files are listed below: - Inkscape: [SVG](https://developer.repebble.com/assets/other/pdc/pencil-inkscape.svg) | [PDC](https://developer.repebble.com/assets/other/pdc/pencil-inkscape.pdc) - Illustrator: [SVG](https://developer.repebble.com/assets/other/pdc/pencil-illustrator.svg) | [PDC](https://developer.repebble.com/assets/other/pdc/pencil-illustrator.pdc) --- # Fonts Source: https://developer.repebble.com/guides/app-resources/fonts/ ## Using Fonts Text drawn in a Pebble app can be drawn using a variety of built-in fonts or a custom font specified as a project resource. Custom font resources must be in the `.ttf` (TrueType font) format. When the app is built, the font file is processed by the SDK according to the `compatibility` (See [_Font Compatibility_](#font-compatibility)) and `characterRegex` fields (see [_Choosing Font Characters_](#choosing-font-characters)), the latter of which is a standard Python regex describing the character set of the resulting font. ## System Fonts All of the built-in system fonts are available to use with [`fonts_get_system_font()`](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_get_system_font "fonts\_get\_system\_font"). See [_System Fonts_](https://developer.repebble.com/guides/app-resources/system-fonts.md) for a complete list with sample images. Examples of using a built-in system font in code are [shown below](#using-a-system-font). ### Limitations There are limitations to the Bitham, Roboto, Droid and LECO fonts, owing to the memory space available on Pebble, which only contain a subset of the default character set. - Roboto 49 Bold Subset - contains digits and a colon. - Bitham 34/42 Medium Numbers - contain digits and a colon. - Bitham 18/34 Light Subset - only contains a few characters and is not suitable for displaying general text. - LECO Number sets - suitable for number-only usage. ## Using a System Font Using a system font is the easiest choice when displaying simple text. For more advanced cases, a custom font may be advantageous. A system font can be obtained at any time, and the developer is not responsible for destroying it when they are done with it. Fonts can be used in two modes: ``` // Use a system font in a TextLayertext\_layer\_set\_font(s\_text\_layer, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_24)); ``` ``` // Use a system font when drawing text manuallygraphics\_draw\_text(ctx, text, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_24), bounds,GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); ``` ## Adding a Custom Font After placing the font file in the project's `resources` directory, the custom font can be added to a project as `font` `type` item in the `media` array in `package.json`. The `name` field's contents will be made available at compile time with `RESOURCE_ID_` at the front, and must end with the desired font size. For example: ``` "resources": {"media": [{"type": "font","name": "EXAMPLE\_FONT\_20","file": "example\_font.ttf"}]} ``` **Important** The maximum recommended font size is 48. ## Using a Custom Font Unlike a system font, a custom font must be loaded and unloaded by the developer. Once this has been done, the font can easily be used in a similar manner. When the app initializes, load the font from resources using the generated `RESOURCE_ID`: ``` // Declare a file-scope variablestatic GFont s\_font; ``` ``` // Load the custom fonts\_font = fonts\_load\_custom\_font(resource\_get\_handle(RESOURCE\_ID\_EXAMPLE\_FONT\_20)); ``` The font can now be used in two modes - with a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"), or when drawing text manually in a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"): ``` // Use a custom font in a TextLayertext\_layer\_set\_font(s\_text\_layer, s\_font); ``` ``` // Use a custom font when drawing text manuallygraphics\_draw\_text(ctx, text, s\_font, bounds, GTextOverflowModeWordWrap,GTextAlignmentCenter, NULL); ``` ## Font Compatibility The font rendering process was improved in SDK 2.8. However, in some cases this may cause the appearance of custom fonts to change slightly. To revert to the old rendering process, add `"compatibility": "2.7"` to your font's object in the `media` array (shown above) in `package.json`. ## Choosing Font Characters By default, the maximum number of supported characters is generated for a font resource. In most cases this will be far too many, and can bloat the size of the app. To optimize the size of your font resources you can use a standard regular expression (or 'regex') string to limit the number of characters to only those you require. The table below outlines some example regular expressions to use for limiting font character sets in common watchapp scenarios: | Expression | Result | | --- | --- | | `[-~]` | ASCII characters only. | | `[0-9]` | Numbers only. | | `[0-9]` | Numbers and spaces only. | | `[a-zA-Z]` | Letters only. | | `[a-zA-Z]` | Letters and spaces only. | | `[0-9:APM]` | Time strings only (e.g.: "12:45 AM"). | | `[0-9:A-Za-z]` | Time and date strings (e.g.: "12:43 AM Wednesday 3rd March 2015". | | `[0-9:A-Za-z°]` | Time, date, and degree symbol for temperature gauges. | | `[0-9°CF]` | Numbers and degree symbol with 'C' and 'F' for temperature gauges. | Add the `characterRegex` key to any font objects in `package.json`'s `media` array. ``` "media": [{"characterRegex": "[:0-9]","type": "font","name": "EXAMPLE\_FONT","file": "example\_font.ttf"}] ``` Check out [regular-expressions.info](http://www.regular-expressions.info/tutorial.html) to learn more about how to use regular expressions. --- # Images Source: https://developer.repebble.com/guides/app-resources/images/ Images can be displayed in a Pebble app by adding them as a project resource. They are stored in memory as a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") while the app is running, and can be displayed either in a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") or by using [`graphics_draw_bitmap_in_rect()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_bitmap_in_rect "graphics\_draw\_bitmap\_in\_rect"). ## Creating an Image In order to be compatible with Pebble, the image should be saved as a PNG file, ideally in a palettized format (see below for palette files) with the appropriate number of colors. The number of colors available on each platform is shown below: | Platform | Number of Colors | | --- | --- | | Aplite | 2 (black and white) | | Basalt | 64 colors | | Chalk | 64 colors | | Diorite | 2 (black and white) | | Emery | 64 colors | | Flint | 2 (black and white) | ## Color Palettes Palette files for popular graphics packages that contain the 64 supported colors are available below. Use these when creating color image resources: - [Photoshop `.act`](https://developer.repebble.com/assets/other/pebble_colors_64.act) - [Aseprite (raw colors, for watch displays) `.aseprite`](https://developer.repebble.com/assets/other/pebble_colors_uncorrected.aseprite) - [Aseprite (Sunlight, color-corrected for HD displays) `.aseprite`](https://developer.repebble.com/assets/other/pebble_colors_sunlight.aseprite) - [Illustrator `.ai`](https://developer.repebble.com/assets/other/pebble_colors_64.ai) - [GIMP `.pal`](https://developer.repebble.com/assets/other/pebble_colors_64.pal) - [ImageMagick `.gif`](https://developer.repebble.com/assets/other/pebble_colors_64.gif) ## Import the Image After placing the image in the project's `resources` directory, add an entry to the `resources` item in `package.json`. Specify the `type` as `bitmap`, choose a `name` (to be used in code) and supply the path relative to the project's `resources` directory. Below is an example: ``` "resources": {"media": [{"type": "bitmap","name": "EXAMPLE\_IMAGE","file": "background.png"}]}, ``` ## Specifying an Image Resource Image resources are used in a Pebble project when they are listed using the `bitmap` resource type. Resources of this type can be optimized using additional attributes: | Attribute | Description | Values | | --- | --- | --- | | `memoryFormat` | Optional. Determines the bitmap type. Reflects values in the `GBitmapFormat` `enum`. | `Smallest`, `SmallestPalette`, `1Bit`, `8Bit`, `1BitPalette`, `2BitPalette`, or `4BitPalette`. | | `storageFormat` | Optional. Determines the file format used for storage. Using `spaceOptimization` instead is preferred. | `pbi` or `png`. | | `spaceOptimization` | Optional. Determines whether the output resource is optimized for low runtime memory or low resource space usage. | `storage` or `memory`. | An example usage of these attributes in `package.json` is shown below: ``` {"type": "bitmap","name": "IMAGE\_EXAMPLE","file": "images/example\_image.png""memoryFormat": "Smallest","spaceOptimization": "memory"} ``` On all platforms `memoryFormat` will default to `Smallest`. On Aplite `spaceOptimization` will default to `memory`, and `storage` on all other platforms. > If you specify a combination of attributes that is not supported, such as a `1Bit` unpalettized PNG, the build will fail. Palettized 1-bit PNGs are supported. When compared to using image resources in previous SDK versions: - `png` is equivalent to `bitmap` with no additional specifiers. - `pbi` is equivalent to `bitmap` with `"memoryFormat": "1Bit"`. - `pbi8` is equivalent to `bitmap` with `"memoryFormat": "8Bit"` and `"storageFormat": "pbi"`. Continuing to use the `png` resource type will result in a `bitmap` resource with `"storageFormat": "png"`, which is not optimized for memory usage on the Aplite platform due to less memory available in total, and is not encouraged. ## Specifying Resources Per Platform To save resource space, it is possible to include only certain image resources when building an app for specific platforms. For example, this is useful for the Aplite platform, which requires only black and white versions of images, which can be significantly smaller in size. Resources can also be selected according to platform and display shape. Read [_Platform-specific Resources_](https://developer.repebble.com/guides/app-resources/platform-specific.md) to learn more about how to do this. ## Displaying an Image Declare a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") pointer. This will be the object type the image data is stored in while the app is running: ``` static GBitmap \*s\_bitmap; ``` Create the [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"), specifying the `name` chosen earlier, prefixed with `RESOURCE_ID_`. This will manage the image data: ``` s\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_EXAMPLE\_IMAGE); ``` Declare a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") pointer: ``` static BitmapLayer \*s\_bitmap\_layer; ``` Create the [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") and set it to show the [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"). Make sure to supply the correct width and height of your image in the [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect"), as well as using [`GCompOpSet`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpSet "GCompOpSet") to ensure color transparency is correctly applied: ``` s\_bitmap\_layer = bitmap\_layer\_create(GRect(5, 5, 48, 48));bitmap\_layer\_set\_compositing\_mode(s\_bitmap\_layer, GCompOpSet);bitmap\_layer\_set\_bitmap(s\_bitmap\_layer, s\_bitmap); ``` Add the [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") as a child layer to the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"): ``` layer\_add\_child(window\_get\_root\_layer(window), bitmap\_layer\_get\_layer(s\_bitmap\_layer)); ``` Destroy both the [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") and [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") when the app exits: ``` gbitmap\_destroy(s\_bitmap);bitmap\_layer\_destroy(s\_bitmap\_layer); ``` ## Menu Icon in the Launcher The new launcher in firmware 4.0+ allows developers to provide a custom icon for their watchapps and watchfaces. ![Launcher Icon](https://developer.repebble.com/assets/images/blog/2016-08-19-pikachu-icon.png) ![Launcher](https://developer.repebble.com/assets/images/blog/2016-08-19-pikachu-launcher.png) You can add a 25x25 `png` to the `resources.media` section of the`package.json` file, and set `"menuIcon": true`. Please note that icons that are larger will be rejected by the SDK. ``` "resources": {"media": [{"type": "bitmap","name": "MENU\_ICON","file": "images/icon.png","menuIcon": true}]} ``` There are four different menu icon display modes, each of which is automatically used based on the platform, type of app, and the way you specify the icon resource. 1. Inversion mode is supported on the black & white platforms, for both apps and watchfaces. This is the mode used by all system icons. In this mode, the icon gets inverted depending on the background on which it is placed. Aplite requires the resource to specify 1Bit memory format and the icon to contain only black and white; transparency and color is not handled. Diorite and Flint don't require the 1Bit memory format to be set for apps (but do for watchfaces), treat any transparency in the file as white, and automatically convert any color to black & white. 2. Non-inverting transparent black & white is an optional mode only for Diorite & Flint watchfaces. The icon is never inverted, and transparency works. If you provide a color icon, it is automatically converted to black & white based on luminance. This mode is enabled when the memory format is not 1Bit. 3. Non-inverting transparent greyscale is the only mode for apps on color platforms. If you provide a color icon, it will be automatically converted to greyscale based on luminance. 4. Non-inverting transparent color is the only mode for watchfaces on color platforms (of course, your icon doesn't have to contain color). Therefore, to support all platforms with the fewest different icons, one option is to provide two files `icon~bw.png` and `icon~color.png` (see [_Platform-specific Resources_](https://developer.repebble.com/guides/app-resources/platform-specific.md)) and add the following to platform.json: ``` "resources": {"media": [{"file": "icon.png","name": "APP\_ICON","type": "bitmap","memoryFormat": "1Bit","menuIcon": true,"targetPlatforms": ["aplite", "diorite", "flint"]},{"file": "icon.png","name": "APP\_ICON","type": "bitmap","targetPlatforms": ["basalt", "chalk", "emery", "gabbro"]},]} ``` If you wish to have a non-inverting transparent icon for Diorite and Flint (watchfaces only) you could specify those two platforms to use a different (non-1Bit) icon than Aplite or, if the automatic black & white conversion is sufficient, to use the same resource as the color platforms. The app icons specified during submission to the appstore are independent of this image resource (used in other places such as the mobile app and the appstore) - the `menuIcon` specified will always be used in the watch launcher list. --- # Pebble Draw Command File Format Source: https://developer.repebble.com/guides/app-resources/pdc-format/ Pebble [`Draw Commands`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands") (PDCs) are vector image files that consist of a binary resource containing the instructions for each stroke, fill, etc. that makes up the image. The byte format of all these components are described in tabular form below. > **Important** : All fields are in the little-endian format! An example implementation with some [usage limitations](https://developer.repebble.com/tutorials/advanced/vector-animations.md#creating-compatible-files) can be seen in [`svg2pdc.py`](https://github.com/pebble-examples/cards-example/blob/master/tools/svg2pdc.py). ## Component Types A PDC binary file consists of the following key components, in ascending order of abstraction: - [Draw Command](#pebble-draw-command) - an instruction for a single line or path to be drawn. - [Draw Command List](#pebble-draw-command-list) - a set of Draw Commands that make up a shape. - [Draw Command Frame](#pebble-draw-command-frame) - a Draw Command List with configurable duration making up one animation frame. Many of these are used in a Draw Command Sequence. - [Draw Command Image](#pebble-draw-command-image) - A single vector image. - [Draw Command Sequence](#pebble-draw-command-sequence) - A set of Draw Command Frames that make up an animated sequence of vector images. ## Versions | PDC Format Version | Implemented | | --- | --- | | 1 | Firmware 3.0 | ## File Format Components ### Point | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | X | 0 | 2 | X axis coordinate. Has one of two formats depending on the Draw Command type (see below): Path/Circle type: signed integer. Precise path type: 13.3 fixed point. | | Y | 2 | 2 | Y axis coordinate. Has one of two formats depending on the Draw Command type (see below): Path/Circle type: signed integer. Precise path type: 13.3 fixed point. | ### View Box | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Width | 0 | 2 | Width of the view box (signed integer). | | Height | 2 | 2 | Height of the view box (signed integer). | ### Pebble Draw Command | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Type | 0 | 1 | Draw command type. Possible values are: `0` - Invalid `1` - Path `2` - Circle `3` - Precise path | | Flags | 1 | 1 | Bit 0: Hidden (Draw Command should not be drawn). Bits 1-7: Reserved. | | Stroke color | 2 | 1 | Pebble color (integer). | | Stroke width | 3 | 1 | Stroke width (unsigned integer). | | Fill color | 4 | 1 | Pebble color (integer). | | Path open/radius | 5 | 2 | Path/Precise path type: Bit 0 indicates whether or not the path is drawn open (`1`) or closed (`0`). Circle type: radius of the circle. | | Number of points | 7 | 2 | Number of points (n) in the point array. See below. | | Point array | 9 | n x 4 | The number of points (n) points. | ### Pebble Draw Command List | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Number of commands | 0 | 2 | Number of Draw Commands in this Draw Command List. (`0` is invalid). | | Draw Command array | 2 | n x size of Draw Command | List of Draw Commands in the format [specified above](#pebble-draw-command). | ### Pebble Draw Command Frame | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Duration | 0 | 2 | Duration of the frame in milliseconds. If `0`, the frame will not be shown at all (unless it is the last frame in a sequence). | | Command list | 2 | Size of Draw Command List | Pebble Draw Command List in the format [specified above](#pebble-draw-command-list). | ### Pebble Draw Command Image | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Version | 8 | 1 | File version. | | Reserved | 9 | 1 | Reserved field. Must be `0`. | | [View box](#view-box) | 10 | 4 | Bounding box of the image. All Draw Commands are drawn relative to the top left corner of the view box. | | Command list | 14 | Size of Draw Command List | Pebble Draw Command List in the format [specified above](#pebble-draw-command-list). | ### Pebble Draw Command Sequence | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Version | 8 | 1 | File version. | | Reserved | 9 | 1 | Reserved field. Must be `0`. | | [View box](#view-box) | 10 | 4 | Bounding box of the sequence. All Draw Commands are drawn relative to the top left corner of the view box. | | Play count | 14 | 2 | Number of times to repeat the sequence. A value of `0` will result in no playback at all, whereas a value of `0xFFFF` will repeat indefinitely. | | Frame count | 16 | 2 | Number of frames in the sequence. `0` is invalid. | | Frame list | 18 | n x size of Draw Command Frame | Array of Draw Command Frames in the format [specified above](#pebble-draw-command-frame). | ## File Formats ### Pebble Draw Command Image File | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Magic word | 0 | 4 | ASCII characters spelling "PDCI". | | Image size | 4 | 4 | Size of the Pebble Draw Command Image (in bytes). | | Image | 8 | Size of Pebble Draw Command Image. | The Draw Command Image in the format [specified above](#pebble-draw-command-image). | ### Pebble Draw Command Sequence File | Field | Offset (bytes) | Size (bytes) | Description | | --- | --- | --- | --- | | Magic word | 0 | 4 | ASCII characters spelling "PDCS". | | Sequence size | 4 | 4 | Size of the Pebble Draw Command Sequence (in bytes). | | Sequence | 8 | Size of Draw Command Sequence | The Draw Command Sequence in the format [specified above](#pebble-draw-command-sequence). | --- # Platform-specific Resources Source: https://developer.repebble.com/guides/app-resources/platform-specific/ You may want to use different versions of a resource on one or more of the Aplite, Basalt or Chalk platforms. To enable this, it is now possible to "tag" resource files with the attributes that make them relevant to a given platform. The follows tags exist for each platform: | Aplite | Basalt | Chalk | Diorite | Emery | Flint | | --- | --- | --- | --- | --- | --- | | rect | rect | round | rect | rect | rect | | bw | color | color | bw | color | bw | | aplite | basalt | chalk | diorite | emery | flint | | 144w | 144w | 180w | 144w | 200w | 144w | | 168h | 168h | 180h | 168h | 228h | 168h | | compass | compass | compass | | compass | compass | | | mic | mic | mic | mic | mic | | | strap | strap | strap | strap | | | | strappower | strappower | | strappower | | | | health | health | health | health | health | To tag a resource, add the tags after the file's using tildes (`~`) — for instance, `example-image~color.png` to use the resource on only color platforms, or `example-image~color~round.png` to use the resource on only platforms with round, color displays. All tags must match for the file to be used. If no file matches for a platform, a compilation error will occur. If the correct file for a platform is ambiguous, an error will occur at compile time. You cannot, for instance, have both `example~color.png` and `example~round.png`, because it is unclear which image to use when building for Chalk. Instead, use `example~color~rect.png` and `example~round.png`. If multiple images could match, the one with the most tags wins. We recommend avoiding the platform specific tags (aplite, basalt etc). When we release new platforms in the future, you will need to create new files for that platform. However, if you use the descriptive tags we will automatically use them as appropriate. It is also worth noting that the platform tags are _not_ special: if you have `example~basalt.png` and `example~rect.png`, that is ambiguous (they both match Basalt) and will cause a compilation error. An example file structure is shown below. ``` my-project/ resources/ images/ example-image~bw.png example-image~color~rect.png example-image~color~round.png src/ main.c package.json wscript ``` This resource will appear in `package.json` as shown below. ``` "resources": {"media": [{"type": "bitmap","name": "EXAMPLE\_IMAGE","file": "images/example-image.png"}]} ``` **Single-platform Resources** If you want to only include a resource on a **specific** platform, you can add a `targetPlatforms` field to the resource's entry in the `media` array in `package.json`. For example, the resource shown below will only be included for the Basalt build. ``` "resources": {"media": [{"type": "bitmap","name": "BACKGROUND\_IMAGE","file": "images/background.png","targetPlatforms": ["basalt"]}]} ``` --- # Raw Data Files Source: https://developer.repebble.com/guides/app-resources/raw-data-files/ Some kinds of apps will require extra data that is not a font or an image. In these cases, the file can be included in a Pebble project as a raw resource. When a file is included as a raw resource, it is not modified in any way from the original when the app is built. Applications of this resource type can be found in the Pebble SDK for APIs such as [`GDrawCommand`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand "GDrawCommand") and [`GBitmapSequence`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapSequence "GBitmapSequence"), which both use raw resources as input files. Other possible applications include localized string dictionaries, CSV data files, etc. ## Adding Raw Data Files To add a file as a raw resource, specify its `type` as `raw` in `package.json`. An example is shown below: ``` "resources": {"media": [{"type": "raw","name": "EXAMPLE\_DATA\_FILE","file": "data.bin"}]} ``` ## Reading Bytes and Byte Ranges Once a raw resource has been added to a project, it can be loaded at runtime in a manner similar to other resources types: ``` // Get resource handleResHandle handle = resource\_get\_handle(RESOURCE\_ID\_DATA); ``` With a handle to the resource now available in the app, the size of the resource can be determined: ``` // Get size of the resource in bytessize\_t res\_size = resource\_size(handle); ``` To read bytes from the resource, create an appropriate byte buffer and copy data into it: ``` // Create a buffer the exact size of the raw resourceuint8\_t \*s\_buffer = (uint8\_t\*)malloc(res\_size); ``` The example below copies the entire resource into a `uint8_t` buffer: ``` // Copy all bytes to a bufferresource\_load(handle, s\_buffer, res\_size); ``` It is also possible to read a specific range of bytes from a given offset into the buffer: ``` // Read the second set of 8 bytesresource\_load\_byte\_range(handle, 8, s\_buffer, 8); ``` --- # System Fonts Source: https://developer.repebble.com/guides/app-resources/system-fonts/ The tables below show all the system font identifiers available in the Pebble SDK, sorted by family. A sample of each is also shown. ## Available System Fonts ### Raster Gothic Available Font Keys | Basalt Preview | Emery Preview || `FONT_KEY_GOTHIC_14` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_14_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_14_emery.png) | | `FONT_KEY_GOTHIC_14_BOLD` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_14_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_14_bold_emery.png) | | `FONT_KEY_GOTHIC_18` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_18_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_18_emery.png) | | `FONT_KEY_GOTHIC_18_BOLD` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_18_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_18_bold_emery.png) | | `FONT_KEY_GOTHIC_24` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_24_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_24_emery.png) | | `FONT_KEY_GOTHIC_24_BOLD` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_24_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_24_bold_emery.png) | | `FONT_KEY_GOTHIC_28` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_28_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_28_emery.png) | | `FONT_KEY_GOTHIC_28_BOLD` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_28_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/gothic_28_bold_emery.png) | ### Bitham Available Font Keys | Basalt Preview | Emery Preview || `FONT_KEY_BITHAM_30_BLACK` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_30_black_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_30_black_emery.png) | | `FONT_KEY_BITHAM_34_MEDIUM_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_34_medium_numbers_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_34_medium_numbers_emery.png) | | `FONT_KEY_BITHAM_42_BOLD` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_42_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_42_bold_emery.png) | | `FONT_KEY_BITHAM_42_LIGHT` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_42_light_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_42_light_emery.png) | | `FONT_KEY_BITHAM_42_MEDIUM_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_42_medium_numbers_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/bitham_42_medium_numbers_emery.png) | ### Roboto/Droid Serif Available Font Keys | Basalt Preview | Emery Preview || `FONT_KEY_ROBOTO_CONDENSED_21` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/roboto_21_condensed_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/roboto_21_condensed_emery.png) | | `FONT_KEY_ROBOTO_BOLD_SUBSET_49` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/roboto_49_bold_subset_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/roboto_49_bold_subset_emery.png) | | `FONT_KEY_DROID_SERIF_28_BOLD` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/droid_28_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/droid_28_bold_emery.png) | ### LECO Available Font Keys | Basalt Preview | Emery Preview || `FONT_KEY_LECO_20_BOLD_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_20_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_20_bold_emery.png) | | `FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_26_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_26_bold_emery.png) | | `FONT_KEY_LECO_28_LIGHT_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_28_light_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_28_light_emery.png) | | `FONT_KEY_LECO_32_BOLD_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_32_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_32_bold_emery.png) | | `FONT_KEY_LECO_36_BOLD_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_36_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_36_bold_emery.png) | | `FONT_KEY_LECO_38_BOLD_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_38_bold_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_38_bold_emery.png) | | `FONT_KEY_LECO_42_NUMBERS` | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_42_preview.png) | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_42_emery.png) | | `FONT_KEY_LECO_60_NUMBERS_AM_PM` | _Emery and newer only_ | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_60_emery.png) | | `FONT_KEY_LECO_60_BOLD_NUMBERS_AM_PM` | _Emery and newer only_ | ![](https://developer.repebble.com/assets/images/guides/app-resources/fonts/leco_60_bold_emery.png) | ## Obtaining System Font Files The following system fonts are available to developers in the SDK can be found online for use in design mockups: - [Raster Gothic](http://www.marksimonson.com/) - By Mark Simonson - [Gotham (Bitham)](http://www.typography.com/fonts/gotham/overview/) - Available from Typography.com - [Droid Serif](https://www.google.com/fonts/specimen/Droid+Serif) - Available from Google Fonts - [LECO 1976](https://www.myfonts.com/fonts/carnoky/leco-1976/) - Available from Myfonts.com ## Using Emoji Fonts A subset of the built-in system fonts support the use of a set of emoji characters. These are the Gothic 24, Gothic 24 Bold, Gothic 18, and Gothic 18 Bold fonts, but do not include the full range. To print an emoji on Pebble, specify the code in a character string like the one shown below when using a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"), or [`graphics_draw_text()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_draw_text "graphics\_draw\_text"): ``` text\_layer\_set\_text(s\_layer, "Smiley face: \U0001F603"); ``` An app containing a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") displaying the above string will look similar to this: ![emoji-screenshot](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-screenshot.png) The supported characters are listed below with their corresponding Unicode codepoints, alongside how each one is rendered on Pebble. _Jumbomoji_ shows the 80×80 SVG that PebbleOS uses when a notification body is just that one emoji. | Codepoint | Symbol | Pebble (small) | Pebble (large) | Jumbomoji | Name | | --- | --- | --- | --- | --- | --- | | `U+231A` | ⌚ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+231A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+231A.png) | | Watch _(new)_ | | `U+263A` | ☺ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+263A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+263A.png) | | White Smiling Face | | `U+2620` | ☠ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+2620.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+2620.png) | | Skull and Crossbones _(new)_ | | `U+26A7` | ⚧ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+26A7.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+26A7.png) | | Transgender Symbol _(new)_ | | `U+2705` | ✅ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+2705.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+2705.png) | | Check Mark Button _(new)_ | | `U+270B` | ✋ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+270B.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+270B.png) | | Raised Hand | | `U+270C` | ✌ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+270C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+270C.png) | | Victory Hand | | `U+2728` | ✨ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+2728.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+2728.png) | | Sparkles _(new)_ | | `U+274E` | ❎ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+274E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+274E.png) | | Cross Mark Button _(new)_ | | `U+2757` | ❗ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+2757.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+2757.png) | | Red Exclamation Mark _(new)_ | | `U+2763` | ❣ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+2763.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+2763.png) | | Heavy Heart Exclamation _(new)_ | | `U+2764` | ❤ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+2764.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+2764.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_heart.svg) | Heavy Black Heart | | `U+2B50` | ⭐ | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+2B50.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+2B50.png) | | Star _(new)_ | | `U+1F319` | 🌙 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F319.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F319.png) | | Crescent Moon _(new)_ | | `U+1F31F` | 🌟 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F31F.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F31F.png) | | Glowing Star _(new)_ | | `U+1F337` | 🌷 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F337.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F337.png) | | Tulip _(new)_ | | `U+1F338` | 🌸 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F338.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F338.png) | | Cherry Blossom _(new)_ | | `U+1F33A` | 🌺 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F33A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F33A.png) | | Hibiscus _(new)_ | | `U+1F340` | 🍀 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F340.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F340.png) | | Four Leaf Clover _(new)_ | | `U+1F37A` | 🍺 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F37A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F37A.png) | | Beer Mug | | `U+1F37B` | 🍻 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F37B.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F37B.png) | | Clinking Beer Mugs | | `U+1F389` | 🎉 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F389.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F389.png) | | Party Popper | | `U+1F3B6` | 🎶 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F3B6.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F3B6.png) | | Musical Notes _(new)_ | | `U+1F3F3` | 🏳 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F3F3.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F3F3.png) | | Waving White Flag _(new)_ | | `U+1F425` | 🐥 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F425.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F425.png) | | Front-Facing Baby Chick | | `U+1F440` | 👀 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F440.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F440.png) | | Eyes _(new)_ | | `U+1F44D` | 👍 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F44D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F44D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_thumbs_up.svg) | Thumbs Up | | `U+1F44E` | 👎 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F44E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F44E.png) | | Thumbs Down | | `U+1F480` | 💀 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F480.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F480.png) | | Skull _(new)_ | | `U+1F493` | 💓 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F493.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F493.png) | | Beating Heart | | `U+1F494` | 💔 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F494.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F494.png) | | Broken Heart | | `U+1F495` | 💕 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F495.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F495.png) | | Two Hearts | | `U+1F496` | 💖 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F496.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F496.png) | | Sparkling Heart | | `U+1F497` | 💗 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F497.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F497.png) | | Growing Heart | | `U+1F498` | 💘 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F498.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F498.png) | | Heart with Arrow | | `U+1F499` | 💙 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F499.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F499.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_heart.svg) | Blue Heart | | `U+1F49A` | 💚 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F49A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F49A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_heart.svg) | Green Heart | | `U+1F49B` | 💛 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F49B.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F49B.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_heart.svg) | Yellow Heart | | `U+1F49C` | 💜 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F49C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F49C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_heart.svg) | Purple Heart | | `U+1F49D` | 💝 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F49D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F49D.png) | | Heart with Ribbon | | `U+1F49E` | 💞 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F49E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F49E.png) | | Revolving Hearts | | `U+1F49F` | 💟 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F49F.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F49F.png) | | Heart Decoration | | `U+1F4A1` | 💡 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F4A1.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F4A1.png) | | Light Bulb _(new)_ | | `U+1F4A3` | 💣 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F4A3.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F4A3.png) | | Bomb _(new)_ | | `U+1F4A5` | 💥 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F4A5.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F4A5.png) | | Collision _(new)_ | | `U+1F4A9` | 💩 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F4A9.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F4A9.png) | | Pile of Poo | | `U+1F4AF` | 💯 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F4AF.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F4AF.png) | | Hundred Points _(new)_ | | `U+1F5A4` | 🖤 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F5A4.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F5A4.png) | | Black Heart _(new)_ | | `U+1F600` | 😀 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F600.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F600.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_big_open_smile.svg) | Grinning Face | | `U+1F601` | 😁 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F601.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F601.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_smiling_with_teeth.svg) | Beaming Face with Smiling Eyes | | `U+1F602` | 😂 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F602.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F602.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_laughing_with_tears.svg) | Face with Tears of Joy | | `U+1F603` | 😃 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F603.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F603.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_big_open_smile.svg) | Smiling Face with Open Mouth | | `U+1F604` | 😄 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F604.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F604.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_big_smile.svg) | Smiling Face with Smiling Eyes | | `U+1F605` | 😅 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F605.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F605.png) | | Smiling Face with Cold Sweat | | `U+1F606` | 😆 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F606.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F606.png) | | Smiling Face with Closed Eyes | | `U+1F607` | 😇 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F607.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F607.png) | | Smiling Face with Halo | | `U+1F608` | 😈 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F608.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F608.png) | | Smiling Face with Horns | | `U+1F609` | 😉 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F609.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F609.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_wink.svg) | Winking Face | | `U+1F60A` | 😊 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F60A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F60A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_smiling_blush.svg) | Smiling Face with Smiling Eyes | | `U+1F60B` | 😋 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F60B.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F60B.png) | | Face Savoring Food | | `U+1F60C` | 😌 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F60C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F60C.png) | | Relieved Face | | `U+1F60D` | 😍 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F60D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F60D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_smiling_hearts.svg) | Smiling Face with Heart-Eyes | | `U+1F60E` | 😎 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F60E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F60E.png) | | Smiling Face with Sunglasses | | `U+1F60F` | 😏 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F60F.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F60F.png) | | Smirking Face | | `U+1F610` | 😐 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F610.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F610.png) | | Neutral Face | | `U+1F611` | 😑 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F611.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F611.png) | | Expressionless Face | | `U+1F612` | 😒 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F612.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F612.png) | | Unamused Face | | `U+1F613` | 😓 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F613.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F613.png) | | Face with Cold Sweat | | `U+1F614` | 😔 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F614.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F614.png) | | Pensive Face | | `U+1F615` | 😕 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F615.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F615.png) | | Confused Face | | `U+1F616` | 😖 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F616.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F616.png) | | Confounded Face | | `U+1F617` | 😗 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F617.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F617.png) | | Kissing Face | | `U+1F618` | 😘 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F618.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F618.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_kissing_with_heart.svg) | Face Blowing a Kiss | | `U+1F619` | 😙 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F619.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F619.png) | | Kissing Face with Smiling Eyes | | `U+1F61A` | 😚 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F61A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F61A.png) | | Kissing Face with Closed Eyes | | `U+1F61B` | 😛 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F61B.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F61B.png) | | Face with Tongue | | `U+1F61C` | 😜 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F61C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F61C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_wink_tongue.svg) | Winking Face with Tongue | | `U+1F61D` | 😝 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F61D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F61D.png) | | Squinting Face with Tongue | | `U+1F61E` | 😞 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F61E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F61E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_sad.svg) | Disappointed Face | | `U+1F61F` | 😟 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F61F.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F61F.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/jumbo/Pebble_80x80_Emoji_sad.svg) | Worried Face | | `U+1F620` | 😠 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F620.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F620.png) | | Angry Face | | `U+1F621` | 😡 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F621.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F621.png) | | Pouting Face | | `U+1F622` | 😢 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F622.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F622.png) | | Crying Face | | `U+1F623` | 😣 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F623.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F623.png) | | Persevering Face | | `U+1F624` | 😤 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F624.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F624.png) | | Face with Steam from Nose | | `U+1F625` | 😥 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F625.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F625.png) | | Sad but Relieved Face | | `U+1F626` | 😦 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F626.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F626.png) | | Frowning Face with Open Mouth | | `U+1F627` | 😧 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F627.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F627.png) | | Anguished Face | | `U+1F628` | 😨 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F628.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F628.png) | | Fearful Face | | `U+1F629` | 😩 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F629.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F629.png) | | Weary Face | | `U+1F62A` | 😪 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F62A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F62A.png) | | Sleepy Face | | `U+1F62B` | 😫 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F62B.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F62B.png) | | Tired Face | | `U+1F62C` | 😬 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F62C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F62C.png) | | Grimacing Face | | `U+1F62D` | 😭 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F62D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F62D.png) | | Loudly Crying Face | | `U+1F62E` | 😮 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F62E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F62E.png) | | Face with Open Mouth | | `U+1F62F` | 😯 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F62F.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F62F.png) | | Hushed Face | | `U+1F630` | 😰 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F630.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F630.png) | | Anxious Face with Sweat | | `U+1F631` | 😱 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F631.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F631.png) | | Face Screaming in Fear | | `U+1F632` | 😲 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F632.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F632.png) | | Astonished Face | | `U+1F633` | 😳 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F633.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F633.png) | | Flushed Face | | `U+1F634` | 😴 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F634.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F634.png) | | Sleeping Face | | `U+1F635` | 😵 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F635.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F635.png) | | Dizzy Face | | `U+1F636` | 😶 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F636.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F636.png) | | Face Without Mouth | | `U+1F637` | 😷 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F637.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F637.png) | | Face with Medical Mask | | `U+1F643` | 🙃 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F643.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F643.png) | | Upside-Down Face _(new)_ | | `U+1F644` | 🙄 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F644.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F644.png) | | Face with Rolling Eyes _(new)_ | | `U+1F64F` | 🙏 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F64F.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F64F.png) | | Folded Hands | | `U+1F917` | 🤗 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F917.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F917.png) | | Hugging Face _(new)_ | | `U+1F918` | 🤘 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F918.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F918.png) | | Sign of the Horns _(new)_ | | `U+1F91D` | 🤝 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F91D.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F91D.png) | | Handshake _(new)_ | | `U+1F923` | 🤣 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F923.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F923.png) | | Rolling on the Floor Laughing _(new)_ | | `U+1F924` | 🤤 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F924.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F924.png) | | Drooling Face _(new)_ | | `U+1F929` | 🤩 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F929.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F929.png) | | Star-Struck _(new)_ | | `U+1F92A` | 🤪 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F92A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F92A.png) | | Zany Face _(new)_ | | `U+1F92C` | 🤬 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F92C.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F92C.png) | | Face with Symbols on Mouth _(new)_ | | `U+1F92E` | 🤮 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F92E.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F92E.png) | | Face Vomiting _(new)_ | | `U+1F970` | 🥰 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F970.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F970.png) | | Smiling Face with Hearts _(new)_ | | `U+1F97A` | 🥺 | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/small/U+1F97A.png) | ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-glyphs/large/U+1F97A.png) | | Pleading Face _(new)_ | ### Deprecated Emoji Symbols The following emoji characters are no longer available on the Aplite platform. ![](https://developer.repebble.com/assets/images/guides/pebble-apps/resources/emoji-unsupported.png) --- # Benefits of Design Guidelines Source: https://developer.repebble.com/guides/design-and-interaction/benefits/ ## What are Design Guidelines? Design guidelines are a set of concepts and rules used to create an app's user interface. These define how the layout on-screen at any one time should be used to maximize the efficiency of presenting data to the user, as well as quickly informing them how to choose their next action. An app creator may look to other popular apps to determine how they have helped their users understand their app's purpose, either through the use of iconography or text highlighting. They may then want to use that inspiration to enable users of the inspiring app to easily use their own app. If many apps use the same visual cues, then future users will already be trained in their use when they discover them. ## What are Interaction Patterns? Similar to design guidelines, interaction patterns define how to implement app interactivity to maximize its efficiency. If a user can predict how the app will behave when they take a certain action, or be able to determine which action fits that which they want to achieve without experimentation, then an intuitive and rewarding experience will result. In addition to purely physical actions such as button presses and accelerometer gestures, the virtual navigation flow should also be considered at the design stage. It should be intuitive for the user to move around the app screens to access the information or execute the commands as they would expect on a first guess. An easy way to achieve this is to use a menu with the items clearly labelling their associated actions. An alternative is to use explicit icons to inform the user implicitly of their purpose without needing to label them all. ## Why are They Needed? Design guidelines and interaction patterns exist to help the developer help the user by ensuring user interface consistency across applications on the platform. It is often the case that the developer will have no problem operating their own watchapp because they have been intimately familiar with how it is supposed to work since its inception. When such an app is given to users, they may receive large amounts of feedback from confused users who feel they do not know if an app supports the functionality they though it did, or even how to find it. By considering a novice user from the beginning of the UI design and implementation, this problem can be avoided. A Pebble watchapp experience is at its best when it can be launched, used for its purpose in the smallest amount of time, and then closed back to the watchface. If the user must spend a long time navigating the app's UI to get to the information they want, or the information takes a while to arrive on every launch, the app efficiency suffers. To avoid this problem, techniques such as implementing a list of the most commonly used options in an app (according to the user or the whole user base) to aid fast navigation, or caching remotely fetched data which may still be relevant from the last update will improve the user experience. From an interaction pattern point of view, a complex layout filled with abstract icons may confuse a first-time user as to what each of them represents. Apps can mitigate this problem by using icons that have pre-established meanings across languages, such as the 'Play/Pause' icon or the 'Power' icon, seen on many forms of devices. ## What Are the Benefits? The main benefits of creating and following design guidelines and common interaction patterns are summarized as follows: - User interface consistency, which breeds familiarity and predictability. - Clarity towards which data is most important and hence visible and usable. - Reduced user confusion and frustration, leading to improved perception of apps. - No need to include explicit usage instructions in every app to explain how it must be used. - Apps that derive design from the system apps can benefit from any learned behavior all Pebble users may develop in using their watches out the box. - Clearer, more efficient and better looking apps! ## Using Existing Affordances Developers can use concepts and interaction patterns already employed in system apps and popular 3rd party apps to lend those affordances to your own apps. An example of this in mobile apps is the common 'swipe down to refresh' action. By using this action in their app, many mobile app makers can benefit from users who have already been trained to perform this action, and can free up their app's UI for a cleaner look, or use the space that would have been used by a 'refresh' button to add an additional feature. In a similar vein, knowing that the Back button always exits the current [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") in a Pebble app, a user does not have to worry about knowing how to navigate out of it. Similarly, developers do not have to repeatedly implement exiting an app, as this action is a single, commonly understood pattern - just press the Back button! On the other hand, if a developer overrides this action a user may be confused or frustrated when the app fails to exit as they would expect, and this could mean a negative opinion that could have been avoided. ## What's Next? Read [_Core Experience Design_](https://developer.repebble.com/guides/design-and-interaction/core-experience.md) to learn how design guidelines helped shape the core Pebble system experience. --- # Core Experience Design Source: https://developer.repebble.com/guides/design-and-interaction/core-experience/ The core Pebble experience includes several built-in system apps that use repeatable design and interaction concepts in their implementation. These are: - Apps are designed with a single purpose in mind, and they do it well. - Fast animations are used to draw attention to changing or updating information. - Larger, bolder fonts to highlight important data. - A preference for displaying multiple data items in a paginated format rather than many 'menus within menus'. This is called the 'card' pattern and is detail in [_Display Data Sets Using Cards_](https://developer.repebble.com/guides/design-and-interaction/recommended.md#display-data-sets-using-cards). - Colors are used to enhance the app's look and feel, and are small in number. Colors are also sometimes used to indicate state, such as the temperature in a weather app, or to differentiate between different areas of a layout. ## System Experience Design The core system design and navigation model is a simple metaphor for time on the user's wrist - a linear representation of the past, present, and future. The first two are presented using the timeline design, with a press of the Up button displaying past events (also known as pins), and a press of the Down button displaying future events. As was the case for previous versions of the system experience, pressing the Select button opens the app menu. This contains the system apps, such as Music and Notifications, as well as all the 3rd party apps the user has installed in their locker. ![system-navigation](https://developer.repebble.com/assets/images/guides/design-and-interaction/system-navigation.png) Evidence of the concepts outlined above in action can be seen within the core system apps in firmware 3.x. These are described in detail in the sections below. ### Music ![music](https://developer.repebble.com/assets/images/guides/design-and-interaction/music.png) The Music app is designed with a singular purpose in mind - display the current song and allow control over playback. To achieve this, the majority of the screen space is devoted to the most important data such as the song title and artist. The remainder of the space is largely used to display the most immediate controls for ease of interaction in an action bar UI element. The 'previous track' and 'next track' icons on the action bar are ones with pre-existing affordances which do not require specific instruction for new users thanks to their universal usaging other media applications. The use of the '...' icon is used as an additional commonly understood action to indicate more functionality is available. By single-pressing this action, the available actions change from preview/next to volume up/volume down, reverting on a timeout. This is preferable to a long-press, which is typically harder to discover without an additional prompt included in the UI. A press of the Back button returns the user to the appface menu, where the Music appface displays the name and artist of the currently playing track, in a scrolling 'marquee' manner. If no music is playing, no information is shown here. ### Notifications ![notifications](https://developer.repebble.com/assets/images/guides/design-and-interaction/notifications.png) The system Notifications app allows a user to access all their past received notifications in one place. Due to the fact that the number of notifications received can be either small or large, the main view of the app is implemented as a menu, with each item showing each notification's icon, title and the first line of the body content. In this way it is easy for a user to quickly scroll down the list and identify the notification they are looking for based on these first hints. The first item in the menu is a 'Clear All' option, which when selected prompts the user to confirm this action using a dialog. This dialog uses the action bar component to give the user the opportunity to confirm this action with the Select button, or to cancel it with the Back button. Once the desired item has been found, a press of the Select button opens a more detailed view, where the complete notification content can be read, scrolling down if needed. The fact that there is more content available to view is hinted at using the arrow marker and overlapping region at the bottom of the layout. ### Alarms ![alarms](https://developer.repebble.com/assets/images/guides/design-and-interaction/alarms.png) The Alarms app is the most complex of the system apps, with multiple screens dedicated to input collection from the user. Like the Watchfaces and Music apps, the appface in the system menu shows the time of the next upcoming scheduled alarm, if any. Also in keeping with other system apps, the main screen is presented using a menu, with each item representing a scheduled alarm. Each alarm is treated as a separate item, containing different settings and values. A press of the Select button on an existing item will open the action menu containing a list of possible actions, such as 'Delete' or 'Disable'. Pressing Select on the top item ('+') will add a new item to the list, using multiple subsequent screens to collect data about the alarm the user wishes to schedule. Using multiple screens avoids the need for one screen to contain a lot of input components and clutter up the display. In the time selection screen, the current selection is marked using a green highlight. The Up and Down buttons are used to increase and decrease the currently selected field respectively. Once a time and recurring frequency has been chosen by the user, the new alarm is added to the main menu list. The default state is enabled, marked by the word 'ON' to the right hand side, but can be disabled in which case 'OFF' is displayed instead. ### Watchfaces ![watchfaces](https://developer.repebble.com/assets/images/guides/design-and-interaction/watchfaces.png) The Watchfaces system app is similar to the Notifications app, in that it uses a menu as its primary means of navigation. Each watchface available in the user's locker is shown as a menu item, with a menu icon if one has been included by the watchface developer. The currently active watchface is indicated by the presence of 'Active' as that item's subtitle. Once the user has selected a new watchface, they are shown a confirmation dialog to let them know their choice was successful. If the watchface is not currently loaded on the watch, a progress bar is shown briefly while the data is loaded. Once this is done the newly chosen watchface is displayed. ### Settings ![settings](https://developer.repebble.com/assets/images/guides/design-and-interaction/settings.png) The Settings app uses the system appface to display the date, the battery charge level, and the Bluetooth connection without the need to open the app proper. If the user does open the app, they are greeted with a menu allowing a choice of settings category. This approach saves the need for a single long list of settings that would require a lot of scrolling. Once a category has been chosen, the app displays another menu filled with interactive menu rows that change various settings. Each item shows the name of the setting in bold as the item title, with the current state of the setting shown as the subtitle. When the user presses Select, the state of the currently selected setting is changed, usually in a binary rotation of On -\> Off states. If the setting does not operate with a binary state (two states), or has more than two options, an action menu window is displayed with the available actions, allowing the user to select one with the Select button. A press of the Back button from a category screen returns the user to the category list, where they can make another selection, or press Back again to return to the app menu. ### Sports API #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/sports~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/sports~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/sports~chalk.png) The Sports API app is designed around displaying the most immediate relevant data of a particular sporting activity, such as running or cycling. A suitable Android or iOS companion app pushes data to this app using the [_PebbleKit Sports API_](https://developer.repebble.com/guides/communication/using-the-sports-api.md) at regular intervals. This API enables third-party sports app developers to easily add support for Pebble without needing to create and maintain their own watchapp. The high contrast choice of colors makes the information easy to read at a glance in a wide variety of lighting conditions, ideal for use in outdoor activities. The action bar is also used to present the main action available to the user - the easily recognizable 'pause' action to suspend the current activity for a break. This is replaced by the equally recognizable 'play' icon, the action now used to resume the activity. This API also contains a separate Golf app for PebbleKit-compatible apps to utilize in tracking the user's golf game. #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/golf~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/golf~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/golf~chalk.png) The Golf app uses a similar design style with larger fonts for important numbers in the center of the layout, with the action bar reserved for additional input, such as moving between holes. Being an app that is intended to be in use for long periods of time, the status bar is used to display the current time for quick reference without needing to exit back to the watchface. ## Timeline Experience Design Evidence of guided design can also be found in other aspects of the system, most noticeably the timeline view. With pins containing possibly a large variety of types of information, it is important to display a view which caters for as many types as possible. As detailed in [_Creating Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md), pins can be shown in two different ways; the event time, icon and title on two lines or a single line, depending on how the user is currently navigating the timeline. When a pin is highlighted, as much information is shown to the user as possible. ![timeline](https://developer.repebble.com/assets/images/guides/design-and-interaction/timeline.png) Users access the timeline view using Up and Down buttons from the watchface to go into the past and future respectively. The navigation of the timeline also uses animations for moving elements that gives life to the user interface, and elements such as moving the pin icon between windows add cohesion between the different screens. A press of the Select button opens the pin to display all the information it contains, and is only one click away. A further press of the Select button opens the pin's action menu, containing a list of all the actions a user may take. These actions are directly related to the pin, and can be specified when it is created. The system provides two default actions: 'Remove' to remove the pin from the user's timeline, and 'Mute [Name]' to mute all future pins from that source. This gives the user control over which pins they see in their personal timeline. Mute actions can be reversed later in the mobile app's 'Apps/Timeline' screen. ## What's Next? Read [_Recommended Guidelines and Patterns_](https://developer.repebble.com/guides/design-and-interaction/recommended.md) for tips on creating an intuitive app experience. --- # One Click Actions Source: https://developer.repebble.com/guides/design-and-interaction/one-click-actions/ One click actions are set to revolutionize the way users interact with their Pebble by providing instant access to their favorite one click watchapps, directly from the new system launcher. Want to unlock your front door? Call an Uber? Or perhaps take an instant voice note? With one click actions, the user is able to instantly perform a single action by launching an app, and taking no further action. ![Lockitron](https://developer.repebble.com/assets/images/guides/design-and-interaction/lockitron.png) ### The One Click Flow It’s important to develop your one click application with a simple and elegant flow. You need to simplify the process of your application by essentially creating an application which serves a single purpose. The typical flow for a one click application would be as follows: 1. Application is launched 2. Application performs action 3. Application displays status to user 4. Application automatically exits to watchface if the action was successful, or displays status message and does not exit if the action failed If we were creating an instant voice note watchapp, the flow could be as follows: 1. Application launched 2. Application performs action (take a voice note) 1. Start listening for dictation 2. Accept dictation response 3. Application displays a success message 4. Exit to watchface In the case of a one click application for something like Uber, we would need to track the state of any existing booking to prevent ordering a second car. We would also want to update the [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") as the status of the booking changes. 1. Application launched 2. If a booking exists: 1. Refresh booking status 2. Update [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") with new status 3. Exit to watchface 3. Application performs action (create a booking) 1. Update AppGlance: “Your Uber is on it’s way” 2. Application displays a success message 3. Exit to watchface ### Building a One Click Application For this example, we’re going to build a one click watchapp which will lock or unlock the front door of our virtual house. We’re going to use a virtual [Lockitron](https://lockitron.com/), or a real one if you’re lucky enough to have one. Our flow will be incredibly simple: 1. Launch the application 2. Take an action (toggle the state of the lock) 3. Update the [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") to indicate the new lock state 4. Display a success message 5. Exit to watchface For the sake of simplicity in our example, we will not know if someone else has locked or unlocked the door using a different application. You can investigate the [Lockitron API](http://api.lockitron.com) if you want to develop this idea further. In order to control our Lockitron, we need the UUID of the lock and an access key. You can generate your own virtual lockitron UUID and access code on the [Lockitron website](https://api.lockitron.com/v1/getting_started/virtual_locks). ``` #define LOCKITRON\_LOCK\_UUID "95c22a11-4c9e-4420-adf0-11f1b36575f2"#define LOCKITRON\_ACCESS\_TOKEN "99e75a775fe737bb716caf88f161460bb623d283c3561c833480f0834335668b" ``` > Never publish your actual Lockitron access token in the appstore, unless you want strangers unlocking your door! Ideally you would make these fields configurable using [Clay for Pebble](https://github.com/pebble-dev/clay). We’re going to need a simple enum for the state of our lock, where 0 is unlocked, 1 is locked and anything else is unknown. ``` typedef enum {LOCKITRON\_UNLOCKED,LOCKITRON\_LOCKED,LOCKITRON\_UNKNOWN} LockitronLockState; ``` We’re also going to use a static variable to keep track of the state of our lock. ``` static LockitronLockState s\_lockitron\_state; ``` When our application launches, we’re going to initialize [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") and then wait for PebbleKit JS to tell us it’s ready. ``` static void prv\_init(void) {app\_message\_register\_inbox\_received(prv\_inbox\_received\_handler);app\_message\_open(256, 256);s\_window = window\_create();window\_stack\_push(s\_window, false);}static void prv\_inbox\_received\_handler(DictionaryIterator \*iter, void \*context) {Tuple \*ready\_tuple = dict\_find(iter, MESSAGE\_KEY\_APP\_READY);if (ready\_tuple) {// PebbleKit JS is ready, toggle the Lockitron!prv\_lockitron\_toggle\_state();return;}// ...} ``` In order to toggle the state of the Lockitron, we’re going to send an [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") to PebbleKit JS, containing our UUID and our access key. ``` static void prv\_lockitron\_toggle\_state() {DictionaryIterator \*out;AppMessageResult result = app\_message\_outbox\_begin(&out);dict\_write\_cstring(out, MESSAGE\_KEY\_LOCK\_UUID, LOCKITRON\_LOCK\_UUID);dict\_write\_cstring(out, MESSAGE\_KEY\_ACCESS\_TOKEN, LOCKITRON\_ACCESS\_TOKEN);result = app\_message\_outbox\_send();} ``` PebbleKit JS will handle this request and make the relevant ajax request to the Lockitron API. It will then return the current state of the lock and tell our application to exit back to the default watchface using [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason"). See the [full example](https://github.com/pebble-examples/one-click-action-example) for the actual Javascript implementation. ``` static void prv\_inbox\_received\_handler(DictionaryIterator \*iter, void \*context) {// ...Tuple \*lock\_state\_tuple = dict\_find(iter, MESSAGE\_KEY\_LOCK\_STATE);if (lock\_state\_tuple) {// Lockitron state has changeds\_lockitron\_state = (LockitronLockState)lock\_state\_tuple-\>value-\>int32;// App will exit to default watchfaceapp\_exit\_reason\_set(APP\_EXIT\_ACTION\_PERFORMED\_SUCCESSFULLY);// Exit the application by unloading the only windowwindow\_stack\_remove(s\_window, false);}} ``` Before our application terminates, we need to update the [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") with the current state of our lock. We do this by passing our current lock state into the [`app_glance_reload`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload") method. ``` static void prv\_deinit(void) {window\_destroy(s\_window);// Before the application terminates, setup the AppGlanceapp\_glance\_reload(prv\_update\_app\_glance, &s\_lockitron\_state);} ``` We only need a single [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") for our [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance"), but it’s worth noting you can have multiple slices with varying expiration times. ``` static void prv\_update\_app\_glance(AppGlanceReloadSession \*session, size\_t limit, void \*context) {// Check we haven't exceeded system limit of AppGlancesif (limit \< 1) return;// Retrieve the current Lockitron state from contextLockitronLockState \*lockitron\_state = context;// Generate a friendly message for the current Lockitron statechar \*str = prv\_lockitron\_status\_message(lockitron\_state);APP\_LOG(APP\_LOG\_LEVEL\_INFO, "STATE: %s", str);// Create the AppGlanceSlice (no icon, no expiry)const AppGlanceSlice entry = (AppGlanceSlice) {.layout = {.template\_string = str},.expiration\_time = time(NULL)+3600};// Add the slice, and check the resultconst AppGlanceResult result = app\_glance\_add\_slice(session, entry);if (result != APP\_GLANCE\_RESULT\_SUCCESS) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "AppGlance Error: %d", result);}} ``` ### Handling Launch Reasons In the example above, we successfully created an application that will automatically execute our One Click Action when the application is launched. But we also need to be aware of some additional launch reasons where it would not be appropriate to perform the action. By using the [`launch_reason()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_reason "launch\_reason") method, we can detect why our application was started and prevent the One Click Action from firing unnecessarily. A common example, would be to detect if the application was actually started by the user, from either the launcher, or quick launch. ``` if(launch\_reason() == APP\_LAUNCH\_USER || launch\_reason() == APP\_LAUNCH\_QUICK\_LAUNCH) {// Perform One Click} else {// Display a message} ``` When the launch reason is [`APP_LAUNCH_USER`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_USER "APP\_LAUNCH\_USER") or [`APP_LAUNCH_QUICK_LAUNCH`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_QUICK_LAUNCH "APP\_LAUNCH\_QUICK\_LAUNCH"), [`launch_button()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_button "launch\_button") returns the [`ButtonId`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId "ButtonId") that launched the app. When it is [`APP_LAUNCH_QUICK_LAUNCH`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_QUICK_LAUNCH "APP\_LAUNCH\_QUICK\_LAUNCH"), [`launch_get_quick_launch_action()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_get_quick_launch_action "launch\_get\_quick\_launch\_action") tells whether the user held a single button ([`APP_QUICK_LAUNCH_ACTION_HOLD`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_HOLD "APP\_QUICK\_LAUNCH\_ACTION\_HOLD")), tapped one ([`APP_QUICK_LAUNCH_ACTION_TAP`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_TAP "APP\_QUICK\_LAUNCH\_ACTION\_TAP")) or held a button combination ([`APP_QUICK_LAUNCH_ACTION_COMBO`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_COMBO "APP\_QUICK\_LAUNCH\_ACTION\_COMBO")); for any other launch reason it returns [`APP_QUICK_LAUNCH_ACTION_NONE`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_NONE "APP\_QUICK\_LAUNCH\_ACTION\_NONE"). An app assigned to more than one Quick Launch slot can use this to perform a different action for each button: ``` if(launch\_reason() == APP\_LAUNCH\_QUICK\_LAUNCH) {switch(launch\_button()) {case BUTTON\_ID\_UP:start\_timer(); break;case BUTTON\_ID\_DOWN: stop\_timer(); break;default: break;}} ``` Both functions are available on Pebble 2 Duo (flint), Pebble Time 2 (emery) and Pebble Round 2 (gabbro). On older platforms they compile to stubs that return [`BUTTON_ID_BACK`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#BUTTON_ID_BACK "BUTTON\_ID\_BACK") and [`APP_QUICK_LAUNCH_ACTION_NONE`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_NONE "APP\_QUICK\_LAUNCH\_ACTION\_NONE"). ### Conclusion As you can see, it’s a relatively small amount of code to create one click watchapps and we hope this inspires you to build your own! We recommend that you check out the complete [Lockitron sample](https://github.com/pebble-examples/one-click-action-example) application and also the [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") and [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") guides for further information. --- # Recommended Guidelines and Patterns Source: https://developer.repebble.com/guides/design-and-interaction/recommended/ This page contains recommendations for things to consider when designing an app's visual styles and interaction patterns. The aim here is to encourage efficiency through optional conformity to common ideas and concepts, and to breed a consistent experience for users across apps. Developers can also find suggested interface styles to use when building the navigation of apps to best display the information contained within. ## Tips for UI Design To achieve an effective, clear, and intuitive design developers should: - Keep layouts **simple** , with only as much information displayed as is **immediately required**. This encourages quick usage of the app, distracting the user for the minimal amount of time necessary for the app to achieve its goals. - Give priority to the **most commonly used/main purpose** functionality of the app in the UI. Make it easy to use the biggest features, but still easy to find additional functionality that would otherwise be hidden. - Use **larger fonts** to highlight the **most important data** to be read at a glance. Consider font size 28 for larger items, and a minimum of 18 for smaller ones. - Take advantage of colors to convey additional **information without any text** if they are already associated as such, such as green for a task complete. - Try to avoid any colors used in places they may have a **pre-conceived meaning** which does not apply, such as red text when there are no errors. - Use animations to embody the layout with **character** and flourish, as well as to **draw the eye** to updated or changing information. - Ensure the UI gives **direct feedback** to the user's input, or else they may think their button presses are having no effect. ## Tips for UI Interaction - Avoid using the Pebble buttons for actions **not** already associated with them, unless clearly marked in the UI using an [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") or similar method. When using the Up and Down buttons for 'previous item' and 'next item' respectively, there may be no need for a visual cue. - Use iconography with **pre-existing visual associations** (such as a 'stop' icon) to take advantage of the inherent behavior the user will have when seeing it. This can avoid the need for explicit instructions to train the user for an app's special case. - Ensure the navigation between app [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")s is **logical** with the input given and the information displayed. For example, an app showing bus timetables should use higher level screens for quickly navigating to a particular area/station, with lower level views reserved for scrolling through the actual timetable data. - If possible, **preserve the state of the app** and/or previous navigation if the app is commonly used for a repetitive task. In the bus timetable example, the user may only look up two stations for a to-from work journey. Learn this behavior and store it with the [Persistent Storage API](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") to intelligently adapt the UI on subsequent launches to show the relevant information. This helps the user avoid navigating through multiple menus every time they launch the app. ## Common Design Styles The following are common design styles that have been successfully used in system and 3rd party apps, and are recommended for use in the correct manner. ### Display Data Sets Using Cards ![card](https://developer.repebble.com/assets/images/guides/design-and-interaction/card.gif) The 'card' style aims to reduce the number of menu levels needed to access as much relevant information as possible. Instead of a menu to select a data set leading to a menu to explore each item in that set, a single [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is designed that displays an entire data set. This view then uses the Pebble Up and Down buttons to scroll through complete data sets in an array of many sets. An example of this is the [_cards-example_](https://developer.repebble.com/guides/design-and-interaction/implementation.md#cards-example-weather) example app, which displays all weather data in a single view and pages through sets of data for separate locations with the Up and Down buttons. This style of UI design allows access to lots of information without navigating through several menus to view it. ### List Options with a Menu #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/list~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/list~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/list~chalk.png) The style is one of the most basic, tried and true styles. Using the [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") UI component, the user may choose between multiple app functions by scrolling with the Up and Down buttons, an interaction pattern afforded to the developer by the core system experience. Using a menu, a user can navigate straight to the part of the app or specific action they want. ### Execute Actions with an ActionBarLayer #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/actionbar~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/actionbar~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/actionbar~chalk.png) The [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") allows easy association of app functionality with the Pebble buttons. By setting icons to each of the three positions, a user can see which actions they can perform at a glance and execture them with a single button press. When pressed, the icon is animated to provide immediate visual feedback. An example of this is the system Music app, that uses the [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") to inform the user that the Up and Down buttons skip tracks. In this case, the Select button is displayed with elipses, indicating further actions are available. A press of this button changes the set of actions on the Up and Down buttons, enabling them to modify the playback volume instead. A collection of icons for common actions is available for use by developers, and can be found in the [_App Assets_](https://developer.repebble.com/guides/app-resources/app-assets.md) guide. ### Allow Extended Options with an Action Menu ![actionmenu](https://developer.repebble.com/assets/images/guides/design-and-interaction/actionmenu.png) If an app screen demands a larger range of available actions than the [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") will allow, present these as a list that slides into view with a press of the Select button using an action menu. This menu contains all the available options, and can contain multiple sub-menus, providing levels. The user can keep track of which level they are currently looking at using the breadcrumb dots on the left-hand side of the screen when the action menu is displayed. Once an action has been chosen, the user should be informed of the success or failure of their choice using a new alert dialog window. In the system action menus, these screens use an eye-catching animation and bold label to convey the result of the action. This feedback is important to prevent the user from getting frustrated if they perceive their input has no result, as well as to reassure them that their action has succeeded without a problem. ### Get User Input with a Form ![list](https://developer.repebble.com/assets/images/guides/design-and-interaction/alarm-list-config.png) Apps such as the system Alarm app make use of a list of configurable items, with each active alarm treated as a menu item with properties. The status of each item is displayed in a menu, with the Select button initiating configuration of that item. When an item is being configured, the data requried to create the item should be obtained from the user through the use of a form, with manipulable elements. In the Alarms example, each integer required to schedule an alarm is obtained with a number field that can have its value incrememted or decremented using the intuitive Up and Down buttons. The current form element is highlighted with color, and advancing to the next element is done with the Select button, doubling as a 'Confirm' action when the end of the form is reached. ### Prompting User Action on the Phone In some applications, user input is required in the app's configuration page (as detailed in [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md)) before the app can perform its task. An example of this is a feed reader app, that will need the user to input the URL of their preferred news feed before it can fetch the feed content. In this case, the watchapp should display a prominent (full- screen if possible) dialog telling the user that input to the phone app for configuration is required. ![action-required](https://developer.repebble.com/assets/images/guides/design-and-interaction/action-required.png) Once the user has performed the required action on the phone, the [`webviewclosed`](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) event should signify that the app can proceed, and that the required data is now available. It should not be the case that this action is required every time the app is started. In most cases, the input data from the user can be stored with [Peristent Storage](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") on the watch, or [`localStorage`](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) on the phone. If the app must get input on every launch (such as a mode selection), this should be done through a form or menu on the watch, so as to avoid needing to use the phone. ### Show Time and Other Data with the Status Bar #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/alarm-list~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/alarm-list~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/alarm-list~chalk.png) Under SDK 2.x, the status bar was displayed to users in all menus and watchapps except watchfaces, or where the developer had explicitly disabled it. This was useful for showing the time and battery level, but arguably not essential all the time. In SDK 3.x, only apps that are designed to be running for extended periods of time (such as Music and the Sports API app) show the time, using the [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer") UI component. The battery level can easily be seen from the Settings appface, and so it not necessary to be always visible. Another instance where the status bar is neccessary is in the Alarms app (shown above), where the user may need to compare with the current time when setting an alarm. If a constant, minimalistic display of app data is required, the [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer") can be used to perform this task. It provides a choice of separator mode and foreground/background colors, and can also be made transparent. Since is it just another [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), it can be easily extended with additional text, icons, or other data. For example, the [`cards-example`](https://github.com/pebble-examples/cards-example) app uses an extention of the status bar to display the currently selected 'card' (a set of displayed data). Another example is the progress bar component example from the [`ui-patterns`](https://github.com/pebble-examples/ui-patterns) app, which builds upon the dotted separator mode to become a thin progress bar. When used in conjunction with the [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") (for example, in the Music system app), the width of the underlying layer should be adjusted such that the time displayed is shown in the new center of the app area (excluding that taken up by the action bar itself). ### Show Alerts and Get Decisions with Modal Windows ![dialog-message](https://developer.repebble.com/assets/images/guides/design-and-interaction/dialog-message.gif) When a significant event occurs while using an app, it should be made visible to the user through the use of a full-screen model dialog. In a similar way that notifications and reminders alert the user to events, these layouts consist of only the important information and an associated icon telling the user the source of the alert, or the reason for its occurrence. This pattern should also be used to quickly and efficently tell the user that an app-related error has occured, including steps on how to fix any potential problems. These alerts can also take the form of requests for important decisions to be made by the user, such as to remember a choice as the default preference: #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/dialog-choice-window~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/dialog-choice-window~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/dialog-choice-window~chalk.png) In this way the decision can be passed to the user with an immediately obvious and actionable set of choices. One the choice has been made, the modal window is dismissed, and a confirmation of the choice displayed. The user should then be returned to the previous window to resume their use of the app where they left off. ### Using Vibrations and Haptic Feedback The Pebble SDK allows the use of the vibration motor to deliver haptic feedback to the user. This can take the form of short, long, double pulses or more detailed vibration sequences, allowing a lot of customization as well as variation between apps. To encourage a consistent experience for users, the [`Vibes`](https://developer.repebble.com/docs/c/User_Interface/Vibes.md "Vibes") API should be used with the following points in mind: - A short pulse should be used to alert the user to the end of a long-running in-app event, such as a download completing, preferably when they are not looking at the watch. - A long pulse should be used to alert the user to a failure or error that requires attention and some interaction. - Custom vibration patterns should be used to allow the user to customize haptic feedback for different events inside the app. When the app is open and being actively interacted with no vibration or haptic feedback should be neccessary on top of the existing visual feedback. However, some exceptions may occur, such as for visually-impaired users. In these cases haptic feedback may be very useful in boosting app accessibility. ### Handling Connection Problems When a watchapp is running, there is no guarantee that the phone connection will be available at any one time. Most apps will function without this connection, but if PebbleKit JS, Android, or iOS is required, the user must be informed of the reason for failure so that they can correct the problem. This check can be performed at any time using [`connection_service_peek_pebble_app_connection()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#connection_service_peek_pebble_app_connection "connection\_service\_peek\_pebble\_app\_connection"). An example alert layout is shown below. #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/no-bt-connection~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/no-bt-connection~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/no-bt-connection~chalk.png) A similar situation arises if an app that requires information or responses from a remote web service attempts to do so, but the phone has no Internet connection. This may be because the user has opted to disable their data connections, or they may be out of range. Another example alert layout is shown below for this situation. #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/no-inet-connection~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/no-inet-connection~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/no-inet-connection~chalk.png) If these kinds of situations can cause problems for the operation of the app, consider using the [`Persistent Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API to cache the most recently loaded data (such as weather, sports scores or news items) from the last successful launch, and display this to the user (while making them aware of the data's age) until new data can be obtained. ### Hiding Timeline-only Apps Watchapps that consist only of a [_Pebble timeline_](https://developer.repebble.com/guides/pebble-timeline.md) experience will only need to be launched when configured by the user to select topic subscriptions. In these cases, developers should hide their app from the launcher menu to prevent the user needlessly launching it. To find out how to do this using the `hiddenApp` property, see [_App Metadata_](https://developer.repebble.com/guides/tools-and-resources/app-metadata.md). ## Consistent App Configuration Watchapps and watchfaces that include user configuration normally include a web page hosted by the app developer, allowing the user to choose from a set of options and apply them to the app. Such options include aesthetic options such as color schemes, larger font sizes, replacement images, data source choices, and others. Traditionally the design of these pages has been left entirely to the developer on a per-app basis, and this is reflected in the resulting design consistency. Read [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) to learn more about configuration page design and implementation. ## What's Next? Read [_Round App Design_](https://developer.repebble.com/guides/design-and-interaction/in-the-round.md) to read tips and guidance on designing apps that work well on a round display. --- # Round App Design Source: https://developer.repebble.com/guides/design-and-interaction/in-the-round/ > This guide is about designing round apps. For advice on implementing a round design in code, read [_Round App UI_](https://developer.repebble.com/guides/user-interfaces/round-app-ui.md). With the release of the Chalk [platform](https://developer.repebble.com/faqs.md#pebble-sdk), developers must take new features and limitations into account when designing their apps. New and existing apps that successfully adapt their layout and colors for both Aplite and Basalt should also endeavor to do so for the Chalk platform. Pebble Round 2 (the Gabbro platform) has a 260 x 260 round display, compared to the 180 x 180 display of Pebble Time Round. The advice in this guide applies to both. Designs that are drawn relative to the display bounds scale between them; designs made from fixed-size bitmaps need a second set of assets for Gabbro. ## Center of Attention With the round Chalk display, apps no longer have the traditional constant amount of horizontal space available. This particularly affects the use of the [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer"). To compensate for this, menus are now always centered on the highlighted item. Use this to display additional information in the cell with the most space available, while showing reduced content previews in the unhighlighted cells. ![centered](https://developer.repebble.com/assets/images/guides/design-and-interaction/center-layout~chalk.png) Menus built using the standard cell drawing functions will automatically adopt this behavior. If performing custom cell drawing, new APIs are available to help implement this behavior. For more information, look at the [`Graphics`](https://developer.repebble.com/docs/c/Graphics.md "Graphics") documentation, as well as the [`menu_layer_set_center_focused()`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_center_focused "menu\_layer\_set\_center\_focused") and [`menu_layer_is_index_selected()`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_is_index_selected "menu\_layer\_is\_index\_selected") to help with conditional drawing. ## Pagination Another key concept to bear in mind when designing for a round display is text flow. In traditional Pebble apps, text in [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") or [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") elements could be freely moved and scrolled with per-pixel increments without issue. However, with a round display each row of text can have a different width, depending on its vertical position. If such text was reflowed while moving smoothly down the window, the layout would reflow so often the text would be very difficult to read. ![center-layout](https://developer.repebble.com/assets/images/guides/design-and-interaction/scrolling-with-text-flow.gif) The solution to this problem is to scroll through text in pages, a technique known as pagination. By moving through the text in discrete sections, the text is only reflowed once per 'page', and remains easily readable as the user is navigating through it. The [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") has been updated to implement this on Chalk. To inform the user that more content is available, the Chalk platform allows use of the [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator") UI component. This facilitates the display of two arrows at the top and bottom of the display, similar to those seen in the system UI. ![content-indicator](https://developer.repebble.com/assets/images/guides/design-and-interaction/content-indicator.png) A [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator") can be created from scratch and manually managed to determine when the arrows should be shown, or a built-in instance can be obtained from a [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer"). ## Platform-Specific Designs Sometimes a design that made sense on a rectangular display does not make sense on a circular one, or could be improved. Be open to creating a new UI for the Chalk platform, and selecting which to use based on the display shape. For example, in the screenshot below the linear track display was incompatible with the round display and center-focused menus, leading to a completely different design on Chalk that shows the same information. #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/caltrain-stops~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/caltrain-stops~chalk.png) ## What's Next? Read [_Example Implementations_](https://developer.repebble.com/guides/design-and-interaction/implementation.md) to learn how to use and implement the UI components and patterns encouraged in SDK 3.x apps. --- # Example Implementations Source: https://developer.repebble.com/guides/design-and-interaction/implementation/ This guide contains resources and links to code examples that may help developers implement UI designs and interaction patterns recommended in the other guides in this section. ## UI Components and Patterns Developers can make use of the many UI components available in SDK 3.x in combination with the [_Common Design Styles_](https://developer.repebble.com/guides/design-and-interaction/recommended.md#common-design-styles) to ensure the user experience is consistent and intuitive. The following components and patterns are used in the Pebble experience, and listed in the table below. Some are components available for developers to use in the SDK, or are example implementations designed for adaptation and re-use. | Pattern | Screenshot | Description | | --- | --- | --- | | [`Menu Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/menulayer.png) | Show many items in a list, allow scrolling between them, and choose an option. | | [`Status Bar`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer") | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/alarm-list~basalt.png) | Display the time at the top of the Window, optionally extended with additional data. | | [`Radio Button List`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/radio_button_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/radio-button.png) | Allow the user to specify one choice out of a list. | | [`Checkbox List`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/checkbox_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/checkbox-list.png) | Allow the user to choose multiple different options from a list. | | [`List Message`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/list_message_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/list-message.png) | Provide a hint to help the user choose from a list of options. | | [`Message Dialog`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/dialog_message_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/dialog-message.gif) | Show an important message using a bold fullscreen alert. | | [`Choice Dialog`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/dialog_choice_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/dialog-choice-patterns.png) | Present the user with an important choice, using the action bar and icons to speed up decision making. | | [`PIN Entry`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/pin_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/pin.png) | Enable the user to input integer data. | | [`Text Animation`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/text_animation_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/text-change-anim.gif) | Example animation to highlight a change in a text field. | | [`Progress Bar`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/progress_bar_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/progress-bar.gif) | Example progress bar implementation on top of a [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer"). | | [`Progress Layer`](https://github.com/pebble-examples/ui-patterns/blob/master/src/windows/progress_layer_window.c) | ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/progresslayer.gif) | Example implementation of the system progress bar layer. | ## Example Apps Developers can look at existing apps to begin to design (or improve) their user interface and interaction design. Many of these apps can be found on the appstore with links to their source code, and can be used as inspiration. ### Cards Example (Weather) The weather [`cards-example`](https://github.com/pebble-examples/cards-example) embodies the 'card' design pattern. Consisting of a single layout, it displays all the crucial weather-related data in summary without the need for further layers of navigation. Instead, the buttons are reserved for scrolling between whole sets of data pertaining to different cities. The number of 'cards' is shown in the top-right hand corner to let the user know that there is more data present to be scrolled through, using the pre-existing Up and Down button action affordances the user has already learned. This helps avoid implementing a novel navigation pattern, which saves time for both the user and the developer. ![weather](https://developer.repebble.com/assets/images/guides/design-and-interaction/weather.gif) When the user presses the appropriate buttons to scroll through sets of data, the changing information is animated with fast, snappy, and highly visible animations to reinforce the idea of old data moving out of the layout and being physically replaced by new data. --- # Design and Interaction Source: https://developer.repebble.com/guides/design-and-interaction/ Interaction guides are intended to help developers design their apps to maximize user experience through effective, consistent visual design and user interactions on the Pebble platform. Readers can be non-programmers and programmers alike: All material is explained conceptually and no code must be understood. For code examples, see [_Example Implementations_](https://developer.repebble.com/guides/design-and-interaction/implementation.md). By designing apps using a commonly understood and easy to understand visual language users can get the best experience with the minimum amount of effort expended - learning how they work, how to operate them or what other behavior is required. This can help boost how efficiently any given app is used as well as help reinforce the underlying patterns for similar apps. For example, the layout design should make it immediately obvious which part of the UI contains the vital information the user should glance at first. In addition to consistent visual design, implementing a common interaction pattern helps an app respond to users as they would expect. This allows them to correctly predict how an app will respond to their input without having to experiment to find out. To get a feel for how to approach good UI design for smaller devices, read other examples of developer design guidelines such as Google's [Material Design](http://www.google.com/design/spec/material-design/introduction.html) page or Apple's [iOS Human Interface Guidelines](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/). ## Contents - [**Benefits of Design Guidelines**](https://developer.repebble.com/guides/design-and-interaction/benefits.md) - Learn the main concepts of design guidelines, why they are needed, and how they can help developers. - [**Core Experience Design**](https://developer.repebble.com/guides/design-and-interaction/core-experience.md) - How design guidelines shape the core Pebble app experience. - [**One Click Actions**](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) - Details about how to create One Click Action watchapps - [**Recommended Guidelines and Patterns**](https://developer.repebble.com/guides/design-and-interaction/recommended.md) - Pebble's recommended guidelines for creating awesome app experiences. - [**Round App Design**](https://developer.repebble.com/guides/design-and-interaction/in-the-round.md) - Tips and advice for designing apps that take advantage of the Pebble Time Round display - [**Example Implementations**](https://developer.repebble.com/guides/design-and-interaction/implementation.md) - Resources and code samples to implement common design and UI patterns. --- # Animations Source: https://developer.repebble.com/guides/graphics-and-animations/animations/ The [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") API allows a variety of different types of value to be smoothly animated from an initial value to a new value over time. Animations can also use built-in easing curves to affect how the transition behaves. ## Using PropertyAnimations The most common use of animations is to move a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") (or similar) around the display. For example, to show or hide some information or animate the time changing in a watchface. The simplest method of animating a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") (such as a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer")) is to use a [`PropertyAnimation`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md "PropertyAnimation"), which animates a property of the target object. In this example, the target is the frame property, which is a [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect") To animate the this property, [`property_animation_create_layer_frame()`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create_layer_frame "property\_animation\_create\_layer\_frame") is used, which is a convenience [`PropertyAnimation`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md "PropertyAnimation") implementation provided by the SDK. ``` static Layer \*s\_layer; ``` Create the Layer during [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") initialization: ``` // Create the Layers\_layer = layer\_create(some\_bounds); ``` Determine the start and end values of the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer")'s frame. These are the 'from' and 'to' locations and sizes of the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") before and after the animation takes place: ``` // The start and end frames - move the Layer 40 pixels to the rightGRect start = GRect(10, 10, 20, 20);GRect finish = GRect(50, 10, 20, 20); ``` At the appropriate time, create a [`PropertyAnimation`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md "PropertyAnimation") to animate the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), specifying the `start` and `finish` values as parameters: ``` // Animate the LayerPropertyAnimation \*prop\_anim = property\_animation\_create\_layer\_frame(s\_layer, &start, &finish); ``` Configure the attributes of the [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation"), such as the delay before starting, and total duration (in milliseconds): ``` // Get the AnimationAnimation \*anim = property\_animation\_get\_animation(prop\_anim);// Choose parametersconst int delay\_ms = 1000;const int duration\_ms = 500;// Configure the Animation's curve, delay, and durationanimation\_set\_curve(anim, AnimationCurveEaseOut);animation\_set\_delay(anim, delay\_ms);animation\_set\_duration(anim, duration\_ms); ``` Finally, schedule the [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") to play at the next possible opportunity (usually immediately): ``` // Play the animationanimation\_schedule(anim); ``` If the app requires knowledge of the start and end times of an [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation"), it is possible to register [`AnimationHandlers`](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationHandlers "AnimationHandlers") to be notified of these events. The handlers should be created with the signature of these examples shown below: ``` static void anim\_started\_handler(Animation \*animation, void \*context) {APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Animation started!");}static void anim\_stopped\_handler(Animation \*animation, bool finished, void \*context) {APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Animation stopped!");} ``` Register the handlers with an optional third context parameter **before** scheduling the [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation"): ``` // Set some handlersanimation\_set\_handlers(anim, (AnimationHandlers) {.started = anim\_started\_handler,.stopped = anim\_stopped\_handler}, NULL); ``` With the handlers registered, the start and end times of the [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") can be detected by the app and used as appropriate. ### Other Types of PropertyAnimation In addition to [`property_animation_create_layer_frame()`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create_layer_frame "property\_animation\_create\_layer\_frame"), it is also possible to animate the origin of a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer")'s bounds using [`property_animation_create_bounds_origin()`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create_bounds_origin "property\_animation\_create\_bounds\_origin"). Animation of more types of data can be achieved using custom implementations and one the following provided update implementations and the associated [getters and setters](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16 "property\_animation\_update\_int16"): - [`property_animation_update_int16`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16 "property\_animation\_update\_int16") - Animate an `int16`. - [`property_animation_update_uint32`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_uint32 "property\_animation\_update\_uint32") - Animate a `uint32`. - [`property_animation_update_gpoint`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gpoint "property\_animation\_update\_gpoint") - Animate a [`GPoint`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint "GPoint"). - [`property_animation_update_grect`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_grect "property\_animation\_update\_grect") - Animate a [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect") - [`property_animation_update_gcolor8`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gcolor8 "property\_animation\_update\_gcolor8") - Animate a [`GColor8`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColor8 "GColor8"). ## Custom Animation Implementations Beyond the convenience functions provided by the SDK, apps can implement their own [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") by using custom callbacks for each stage of the animation playback process. A [`PropertyAnimation`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md "PropertyAnimation") is an example of such an implementation. The callbacks to implement are the `.setup`, `.update`, and `.teardown` members of an [`AnimationImplementation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationImplementation "AnimationImplementation") object. Some example implementations are shown below. It is in the `.update` callback where the value of `progress` can be used to modify the custom target of the animation. For example, some percentage of completion: ``` static void implementation\_setup(Animation \*animation) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Animation started!");}static void implementation\_update(Animation \*animation, const AnimationProgress progress) {// Animate some completion variables\_animation\_percent = ((int)progress \* 100) / ANIMATION\_NORMALIZED\_MAX;APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Animation progress: %d%%", s\_animation\_percent);}static void implementation\_teardown(Animation \*animation) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Animation finished!");}// This needs to exist while the event loop runsstatic const AnimationImplementation s\_implementation = {.setup = implementation\_setup,.update = implementation\_update,.teardown = implementation\_teardown}; ``` Once these are in place, create a new [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") , specifying the new custom implementation as a `const` object pointer at the appropriate time: ``` // Create a new AnimationAnimation \*animation = animation\_create();animation\_set\_delay(animation, 1000);animation\_set\_duration(animation, 1000);// Create the AnimationImplementationanimation\_set\_implementation(animation, &s\_implementation);// Play the Animationanimation\_schedule(animation); ``` The output of the example above will look like the snippet shown below (edited for brevity). Note the effect of the easing [`AnimationCurve`](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve "AnimationCurve") on the progress value: ``` [13:42:33] main.c:11> Animation started! [13:42:34] main.c:19> Animation progress: 0% [13:42:34] main.c:19> Animation progress: 0% [13:42:34] main.c:19> Animation progress: 0% [13:42:34] main.c:19> Animation progress: 2% [13:42:34] main.c:19> Animation progress: 3% [13:42:34] main.c:19> Animation progress: 5% [13:42:34] main.c:19> Animation progress: 7% [13:42:34] main.c:19> Animation progress: 10% [13:42:34] main.c:19> Animation progress: 14% [13:42:35] main.c:19> Animation progress: 17% [13:42:35] main.c:19> Animation progress: 21% [13:42:35] main.c:19> Animation progress: 26% ... [13:42:35] main.c:19> Animation progress: 85% [13:42:35] main.c:19> Animation progress: 88% [13:42:35] main.c:19> Animation progress: 91% [13:42:35] main.c:19> Animation progress: 93% [13:42:35] main.c:19> Animation progress: 95% [13:42:35] main.c:19> Animation progress: 97% [13:42:35] main.c:19> Animation progress: 98% [13:42:35] main.c:19> Animation progress: 99% [13:42:35] main.c:19> Animation progress: 99% [13:42:35] main.c:19> Animation progress: 100% [13:42:35] main.c:23> Animation finished! ``` ## Timers [`AppTimer`](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer") objects can be used to schedule updates to variables and objects at a later time. They can be used to implement frame-by-frame animations as an alternative to using the [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") API. They can also be used in a more general way to schedule events to occur at some point in the future (such as UI updates) while the app is open. A thread-blocking alternative for small pauses is [`psleep()`](https://developer.repebble.com/docs/c/Foundation/Timer.md#psleep "psleep"), but this is **not** recommended for use in loops updating UI (such as a counter), or for scheduling [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") messages, which rely on the event loop to do their work. > Note: To create timed events in the future that persist after an app is closed, check out the [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup") API. When a timer elapses, it will call a developer-defined [`AppTimerCallback`](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimerCallback "AppTimerCallback"). This is where the code to be executed after the timed interval should be placed. The callback will only be called once, so use this opportunity to re-register the timer if it should repeat. ``` static void timer\_callback(void \*context) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Timer elapsed!");} ``` Schedule the timer with a specific `delay` interval, the name of the callback to fire, and an optional context pointer: ``` const int delay\_ms = 5000;// Schedule the timerapp\_timer\_register(delay\_ms, timer\_callback, NULL); ``` If the timer may need to be cancelled or rescheduled at a later time, ensure a reference to it is kept for later use: ``` static AppTimer \*s\_timer; ``` ``` // Register the timer, and keep a handle to its\_timer = app\_timer\_register(delay\_ms, timer\_callback, NULL); ``` If the timer needs to be cancelled, use the previous reference. If it has already elapsed, nothing will occur: ``` // Cancel the timerapp\_timer\_cancel(s\_timer); ``` ## Sequence and Spawn Animations The Pebble SDK also includes the capability to build up composite animations built from other [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") objects. There are two types: a sequence animation and a spawn animation. - A sequence animation is a set of two or more other animations that are played out in **series** (one after another). For example, a pair of timed animations to show and hide a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"). - A spawn animation is a set of two or more other animations that are played out in **parallel**. A spawn animation acts the same as creating and starting two or more animations at the same time, but has the advantage that it can be included as part of a sequence animation. > Note: Composite animations can be composed of other composite animations. ### Important Considerations When incorporating an [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") into a sequence or spawn animation, there are a couple of points to note: - Any single animation cannot appear more than once in the list of animations used to create a more complex animation. - A composite animation assumes ownership of its component animations once it has been created. - Once an animation has been added to a composite animation, it becomes immutable. This means it can only be read, and not written to. Attempts to modify such an animation after it has been added to a composite animation will fail. - Once an animation has been added to a composite animation, it cannot then be used to build a different composite animation. ### Creating a Sequence Animation To create a sequence animation, first create the component [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") objects that will be used to build it. ``` // Create the first AnimationPropertyAnimation \*prop\_anim = property\_animation\_create\_layer\_frame(s\_layer, &start, &finish);Animation \*animation\_a = property\_animation\_get\_animation(prop\_anim);// Set some propertiesanimation\_set\_delay(animation\_a, 1000);animation\_set\_duration(animation\_a, 500);// Clone the first, modify the duration and reverse it.Animation \*animation\_b = animation\_clone(animation\_a);animation\_set\_reverse(animation\_b, true);animation\_set\_duration(animation\_b, 1000); ``` Use these component animations to create the sequence animation. You can either specify the components as a list or pass an array. Both approaches are shown below. #### Using a List You can specify up to 20 [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") objects as parameters to `animation_sequence_create()`. The list must always be terminated with `NULL` to mark the end. ``` // Create the sequenceAnimation \*sequence = animation\_sequence\_create(animation\_a, animation\_b, NULL);// Play the sequenceanimation\_schedule(sequence); ``` #### Using an Array You can also specify the component animations using a dynamically allocated array. Give this to `animation_sequence_create_from_array()` along with the size of the array. ``` const uint32\_t array\_length = 2;// Create the arrayAnimation \*\*arr = (Animation\*\*)malloc(array\_length \* sizeof(Animation\*));arr[0] = animation\_a;arr[1] = animation\_b;// Create the sequence, set to loop foreverAnimation \*sequence = animation\_sequence\_create\_from\_array(arr, array\_length);animation\_set\_play\_count(sequence, ANIMATION\_DURATION\_INFINITE);// Play the sequenceanimation\_schedule(sequence);// Destroy the arrayfree(arr); ``` ### Creating a Spawn Animation Creating a spawn animation is done in a very similiar way to a sequence animation. The animation is built up from component animations which are then all started at the same time. This simplifies the task of precisely timing animations that are designed to coincide. The first step is the same as for sequence animations, which is to create a number of component animations to be spawned together. ``` // Create the first animationAnimation \*animation\_a = animation\_create();animation\_set\_duration(animation\_a, 1000);// Clone the first, modify the duration and reverse it.Animation \*animation\_b = animation\_clone(animation\_a);animation\_set\_reverse(animation\_b, true);animation\_set\_duration(animation\_b, 300); ``` Next, the spawn animation is created in a similar manner to the sequence animation with a `NULL` terminated list of parameters: ``` // Create the spawn animationAnimation \*spawn = animation\_spawn\_create(animation\_a, animation\_b, NULL);// Play the animationanimation\_schedule(spawn); ``` Alternatively the spawn animation can be created with an array of [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") objects. ``` const uint32\_t array\_length = 2;// Create the arrayAnimation \*\*arr = (Animation\*\*)malloc(array\_length \* sizeof(Animation\*));arr[0] = animation\_a;arr[1] = animation\_b;// Create the sequence and set the play count to 3Animation \*spawn = animation\_spawn\_create\_from\_array(arr, array\_length);animation\_set\_play\_count(spawn, 3);// Play the spawn animationanimation\_schedule(spawn);// Destroy the arrayfree(arr); ``` --- # Drawing Primitives, Images and Text Source: https://developer.repebble.com/guides/graphics-and-animations/drawing-primitives-images-and-text/ While [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") types such as [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") and [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") allow easy rendering of text and bitmaps, more precise drawing can be achieved through the use of the [`Graphics Context`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md "Graphics Context") APIs. Custom drawing of primitive shapes such as line, rectangles, and circles is also supported. Clever use of these functions can remove the need to pre-prepare bitmap images for many UI elements and icons. ## Obtaining a Drawing Context All custom drawing requires a [`GContext`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GContext "GContext") instance. These cannot be created, and are only available inside a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"). This update procedure is simply a function that is called when a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") is to be rendered, and is defined by the developer as opposed to the system. For example, a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") is simply a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") with a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") abstracted away for convenience by the SDK. First, create the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") that will have a custom drawing procedure: ``` static Layer \*s\_canvas\_layer; ``` Allocate the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") during [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") creation: ``` GRect bounds = layer\_get\_bounds(window\_get\_root\_layer(window));// Create canvas layers\_canvas\_layer = layer\_create(bounds); ``` Next, define the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") according to the function specification: ``` static void canvas\_update\_proc(Layer \*layer, GContext \*ctx) {// Custom drawing happens here!} ``` Assign this procedure to the canvas layer and add it to the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") to make it visible: ``` // Assign the custom drawing procedurelayer\_set\_update\_proc(s\_canvas\_layer, canvas\_update\_proc);// Add to Windowlayer\_add\_child(window\_get\_root\_layer(window), s\_canvas\_layer); ``` From now on, every time the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") needs to be redrawn (for example, if other layer geometry changes), the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") will be called to allow the developer to draw it. It can also be explicitly marked for redrawing at the next opportunity: ``` // Redraw this as soon as possiblelayer\_mark\_dirty(s\_canvas\_layer); ``` ## Drawing Primitive Shapes The [`Graphics Context`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md "Graphics Context") API allows drawing and filling of lines, rectangles, circles, and arbitrary paths. For each of these, the colors of the output can be set using the appropriate function: ``` // Set the line colorgraphics\_context\_set\_stroke\_color(ctx, GColorRed);// Set the fill colorgraphics\_context\_set\_fill\_color(ctx, GColorBlue); ``` In addition, the stroke width and antialiasing mode can also be changed: ``` // Set the stroke width (must be an odd integer value)graphics\_context\_set\_stroke\_width(ctx, 5);// Disable antialiasing (enabled by default where available)graphics\_context\_set\_antialiased(ctx, false); ``` ### Lines Drawing a simple line requires only the start and end positions, expressed as [`GPoint`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint "GPoint") values: ``` GPoint start = GPoint(10, 10);GPoint end = GPoint(40, 60);// Draw a linegraphics\_draw\_line(ctx, start, end); ``` ### Rectangles Drawing a rectangle requires a bounding [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect"), as well as other parameters if it is to be filled: ``` GRect rect\_bounds = GRect(10, 10, 40, 60);// Draw a rectanglegraphics\_draw\_rect(ctx, rect\_bounds);// Fill a rectangle with rounded cornersint corner\_radius = 10;graphics\_fill\_rect(ctx, rect\_bounds, corner\_radius, GCornersAll); ``` It is also possible to draw a rounded unfilled rectangle: ``` // Draw outline of a rounded rectanglegraphics\_draw\_round\_rect(ctx, rect\_bounds, corner\_radius); ``` ### Circles Drawing a circle requries its center [`GPoint`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint "GPoint") and radius: ``` GPoint center = GPoint(25, 25);uint16\_t radius = 50;// Draw the outline of a circlegraphics\_draw\_circle(ctx, center, radius);// Fill a circlegraphics\_fill\_circle(ctx, center, radius); ``` In addition, it is possble to draw and fill arcs. In these cases, the [`GOvalScaleMode`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleMode "GOvalScaleMode") determines how the shape is adjusted to fill the rectangle, and the cartesian angle values are transformed to preserve accuracy: ``` int32\_t angle\_start = DEG\_TO\_TRIGANGLE(0);int32\_t angle\_end = DEG\_TO\_TRIGANGLE(45);// Draw an arcgraphics\_draw\_arc(ctx, rect\_bounds, GOvalScaleModeFitCircle, angle\_start, angle\_end); ``` Lastly, a filled circle with a sector removed can also be drawn in a similar manner. The value of `inset_thickness` determines the inner inset size that is removed from the full circle: ``` uint16\_t inset\_thickness = 10; // Fill a radial section of a circlegraphics\_fill\_radial(ctx, rect\_bounds, GOvalScaleModeFitCircle, inset\_thickness,angle\_start, angle\_end); ``` For more guidance on using round elements in apps, watch the presentation given at the 2015 Developer Retreat on [developing for Pebble Time Round](https://www.youtube.com/watch?v=3a1V4n9HDvY). ## Bitmaps Manually drawing [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") images with the [`Graphics Context`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md "Graphics Context") API is a simple task, and has much in common with the alternative approach of using a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") (which provides additional convenience funcionality). The first step is to load the image data from resources (read [_Images_](https://developer.repebble.com/guides/app-resources/images.md) to learn how to include images in a Pebble project): ``` static GBitmap \*s\_bitmap; ``` ``` // Load the image datas\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_EXAMPLE\_IMAGE); ``` When the appropriate [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") is called, draw the image inside the desired rectangle: > Note: Unlike [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer"), the image will be drawn relative to the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer")'s origin, and not centered. ``` // Get the bounds of the imageGRect bitmap\_bounds = gbitmap\_get\_bounds(s\_bitmap);// Set the compositing mode (GCompOpSet is required for transparency)graphics\_context\_set\_compositing\_mode(ctx, GCompOpSet);// Draw the imagegraphics\_draw\_bitmap\_in\_rect(ctx, s\_bitmap, bitmap\_bounds); ``` Once the image is no longer needed (i.e.: the app is exiting), free the data: ``` // Destroy the image datagbitmap\_destroy(s\_bitmap); ``` ## Drawing Text Similar to the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") UI component, a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") can also be used to draw text. Advantages can include being able to draw in multiple fonts with only one [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") and combining text with other drawing operations. The first operation to perform inside the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") is to get or load the font to be used for drawing and set the text's color: ``` // Load the fontGFont font = fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_24\_BOLD);// Set the colorgraphics\_context\_set\_text\_color(ctx, GColorBlack); ``` Next, determine the bounds that will guide the text's position and overflow behavior. This can either be the size of the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), or a more precise bounds of the text itself. This information can be useful for drawing multiple text items after one another with automatic spacing. ``` char \*text = "Example test string for the Developer Website guide!";// Determine a reduced bounding boxGRect layer\_bounds = layer\_get\_bounds(layer);GRect bounds = GRect(layer\_bounds.origin.x, layer\_bounds.origin.y,layer\_bounds.size.w / 2, layer\_bounds.size.h);// Calculate the size of the text to be drawn, with restricted spaceGSize text\_size = graphics\_text\_layout\_get\_content\_size(text, font, bounds,GTextOverflowModeWordWrap, GTextAlignmentCenter); ``` Finally, the text can be drawn into the appropriate bounding rectangle: ``` // Draw the textgraphics\_draw\_text(ctx, text, font, bounds, GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); ``` --- # Framebuffer Graphics Source: https://developer.repebble.com/guides/graphics-and-animations/framebuffer-graphics/ In the context of a Pebble app, the framebuffer is the data region used to store the contents of the what is shown on the display. Using the [`Graphics Context`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md "Graphics Context") API allows developers to draw primitive shapes and text, but at a slower speed and with a restricted set of drawing patterns. Getting direct access to the framebuffer allows arbitrary transforms, special effects, and other modifications to be applied to the display contents, and allows drawing at a much greater speed than standard SDK APIs. ## Accessing the Framebuffer Access to the framebuffer can only be obtained during a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"), when redrawing is taking place. When the time comes to update the associated [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), the framebuffer can be obtained as a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"): ``` static void layer\_update\_proc(Layer \*layer, GContext \*ctx) {// Get the framebufferGBitmap \*fb = graphics\_capture\_frame\_buffer(ctx);// Manipulate the image data...// Finally, release the framebuffergraphics\_release\_frame\_buffer(ctx, fb);} ``` > Note: Once obtained, the framebuffer **must** be released back to the app so that it may continue drawing. The format of the data returned will vary by platform, as will the representation of a single pixel, shown in the table below. | Platform | Framebuffer Bitmap Format | Pixel Format | | --- | --- | --- | | Aplite | [`GBitmapFormat1Bit`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat1Bit "GBitmapFormat1Bit") | One bit (black or white) | | Basalt | [`GBitmapFormat8Bit`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8Bit "GBitmapFormat8Bit") | One byte (two bits per color) | | Chalk | [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular") | One byte (two bits per color) | | Diorite | [`GBitmapFormat1Bit`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat1Bit "GBitmapFormat1Bit") | One bit (black or white) | | Flint | [`GBitmapFormat1Bit`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat1Bit "GBitmapFormat1Bit") | One bit (black or white) | | Emery | [`GBitmapFormat8Bit`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8Bit "GBitmapFormat8Bit") | One byte (two bits per color) | | Gabbro | [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular") | One byte (two bits per color) | Gabbro reports [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular") like Chalk, but its rows are not packed: the framebuffer is a full 260 x 260 byte rectangle, every row starts 260 bytes after the previous one, and only the `min_x` to `max_x` range of each row is inside the circle. Apps built for Chalk and run on Gabbro get the packed 180 x 180 Chalk layout instead. On both platforms [`gbitmap_get_bytes_per_row()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_bytes_per_row "gbitmap\_get\_bytes\_per\_row") returns 0 for this format, so always use [`gbitmap_get_data_row_info()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info "gbitmap\_get\_data\_row\_info") to address pixels rather than a fixed stride. ## Modifying the Framebuffer Data Once the framebuffer has been captured, the underlying data can be manipulated on a row-by-row or even pixel-by-pixel basis. This data region can be obtained using [`gbitmap_get_data()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data "gbitmap\_get\_data"), but the recommended approach is to make use of [`gbitmap_get_data_row_info()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info "gbitmap\_get\_data\_row\_info") objects to cater for framebuffer formats (such as [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular") on Chalk) where not every row is of the same width. The [`GBitmapDataRowInfo`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapDataRowInfo "GBitmapDataRowInfo") object helps with this by providing a `min_x` and `max_x` value for each `y` used to build it. Using it on platforms with a regular rectangular framebuffer is still safe — `min_x` and `max_x` will simply span the full row width. To iterate over all rows and columns, safely avoiding those with irregular start and end indices, use two nested loops as shown below. The implementation of `set_pixel_color()` is shown in [_Getting and Setting Pixels_](#getting-and-setting-pixels): > Note: it is only necessary to call [`gbitmap_get_data_row_info()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info "gbitmap\_get\_data\_row\_info") once per row. Calling it more often (such as for every pixel) will incur a sigificant speed penalty. ``` GRect bounds = layer\_get\_bounds(layer);// Iterate over all rowsfor(int y = 0; y \< bounds.size.h; y++) {// Get this row's range and dataGBitmapDataRowInfo info = gbitmap\_get\_data\_row\_info(fb, y);// Iterate over all visible columnsfor(int x = info.min\_x; x \<= info.max\_x; x++) {// Manipulate the pixel at x,y...const GColor random\_color = (GColor){ .argb = rand() % 255 };// ...to be a random colorset\_pixel\_color(info, GPoint(x, y), random\_color);}} ``` ## Getting and Setting Pixels To modify a pixel's value, simply set a new value at the appropriate position in the `data` field of that row's [`GBitmapDataRowInfo`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapDataRowInfo "GBitmapDataRowInfo") object. This will modify the underlying data, and update the display once the frame buffer is released. This process will be different depending on the [`GBitmapFormat`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat "GBitmapFormat") of the captured framebuffer. On a color platform, each pixel is stored as a single byte. However, on black and white platforms this will be one bit per byte. Using [`memset()`](https://developer.repebble.com/docs/c/Standard_C/Memory.md#memset "memset") to read or modify the correct pixel on a black and white display requires a bit more logic, shown below: ``` static GColor get\_pixel\_color(GBitmapDataRowInfo info, GPoint point) {#if defined(PBL\_COLOR)// Read the single byte color pixelreturn (GColor){ .argb = info.data[point.x] };#elif defined(PBL\_BW)// Read the single bit of the correct byteuint8\_t byte = point.x / 8;uint8\_t bit = point.x % 8; return byte\_get\_bit(&info.data[byte], bit) ? GColorWhite : GColorBlack;#endif} ``` Setting a pixel value is achieved in much the same way, with different logic depending on the format of the framebuffer on each platform: ``` static void set\_pixel\_color(GBitmapDataRowInfo info, GPoint point, GColor color) {#if defined(PBL\_COLOR)// Write the pixel's byte colormemset(&info.data[point.x], color.argb, 1);#elif defined(PBL\_BW)// Find the correct byte, then set the appropriate bituint8\_t byte = point.x / 8;uint8\_t bit = point.x % 8; byte\_set\_bit(&info.data[byte], bit, gcolor\_equal(color, GColorWhite) ? 1 : 0);#endif} ``` The `byte_get_bit()` and `byte_set_bit()` implementations are shown here for convenience: ``` static bool byte\_get\_bit(uint8\_t \*byte, uint8\_t bit) {return ((\*byte) \>\> bit) & 1;}static void byte\_set\_bit(uint8\_t \*byte, uint8\_t bit, uint8\_t value) {\*byte ^= (-value ^ \*byte) & (1 \<\< bit);} ``` ## Learn More To see an example of what can be achieved with direct access to the framebuffer and learn more about the underlying principles, watch the [talk given at the 2014 Developer Retreat](https://www.youtube.com/watch?v=lYoHh19RNy4). --- # Graphics and Animations Source: https://developer.repebble.com/guides/graphics-and-animations/ The Pebble SDK allows drawing of many types of graphics in apps. Using the [`Graphics Context`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md "Graphics Context"), [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"), [`GDrawCommand`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand "GDrawCommand") and [`Framebuffer`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_capture_frame_buffer "graphics\_capture\_frame\_buffer") APIs gives developers complete control over the contents of the display, and also can be used to complement UI created with the various types of [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") available to enable more complex UI layouts. Both image-based and property-based animations are available, as well as scheduling regular updates to UI components with [`Timer`](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer") objects. Creative use of [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") and [`PropertyAnimation`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md "PropertyAnimation") can help to delight and engage users, as well as highlight important information inside apps. ## Contents - [**Animations**](https://developer.repebble.com/guides/graphics-and-animations/animations.md) - How to use Animations and Timers to add life to your app. - [**Drawing Primitives, Images and Text**](https://developer.repebble.com/guides/graphics-and-animations/drawing-primitives-images-and-text.md) - How to draw primitive shapes, image, and text onto the Graphics Context. - [**Framebuffer Graphics**](https://developer.repebble.com/guides/graphics-and-animations/framebuffer-graphics.md) - How to perform advanced drawing using direct framebuffer access. - [**Vector Graphics**](https://developer.repebble.com/guides/graphics-and-animations/vector-graphics.md) - How to draw simple images using vector images, instead of bitmaps. --- # Vector Graphics Source: https://developer.repebble.com/guides/graphics-and-animations/vector-graphics/ This is an overview of drawing vector images using Pebble Draw Command files. See the [_Vector Animations_](https://developer.repebble.com/tutorials/advanced/vector-animations.md) tutorial for more information. ## Vector Graphics on Pebble As opposed to bitmaps which contain data for every pixel to be drawn, a vector file contains only instructions about points contained in the image and how to draw lines connecting them up. Instructions such as fill color, stroke color, and stroke width are also included. Vector images on Pebble are implemented using the [`Draw Commands`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md "Draw Commands") API, which allows apps to load and display PDC (Pebble Draw Command) images and sequences that contain sets of these instructions. An example is the weather icon used in weather timeline pins. The benefit of using vector graphics for this icon is that is allows the image to stretch in the familiar manner as it moves between the timeline view and the pin detail view: ![weather](https://developer.repebble.com/assets/images/tutorials/advanced/weather.png) The main benefits of vectors over bitmaps for simple images and icons are: - Smaller resource size - instructions for joining points are less memory expensive than per-pixel bitmap data. - Flexible rendering - vector images can be rendered as intended, or manipulated at runtime to move the individual points around. This allows icons to appear more organic and life-like than static PNG images. Scaling and distortion is also made possible. However, there are also some drawbacks to choosing vector images in certain cases: - Vector files require more specialized tools to create than bitmaps, and so are harder to produce. - Complicated vector files may take more time to render than if they were simply drawn per-pixel as a bitmap, depending on the drawing implementation. ## Creating Compatible Files The file format of vector image files on Pebble is the PDC (Pebble Draw Command) format, which includes all the instructions necessary to allow drawing of vectors. These files are created from compatible SVG (Scalar Vector Graphics) files. Read [_Converting SVG to PDC_](https://developer.repebble.com/guides/app-resources/converting-svg-to-pdc.md) for more information. Pebble Draw Command files can only be used from app resources, and cannot be created at runtime. ## Drawing Vector Graphics Add the PDC file to the project resources in `package.json` with the 'type' field to `raw`: ``` "media": [{"type": "raw","name": "EXAMPLE\_IMAGE","file": "example\_image.pdc"}] ``` Drawing a Pebble Draw Command image is just as simple as drawing a normal PNG image to a graphics context, requiring only one draw call. First, load the `.pdc` file from resources as shown below. First, declare a pointer of type [`GDrawCommandImage`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage "GDrawCommandImage") at the top of the file: ``` static GDrawCommandImage \*s\_command\_image; ``` Create and assign the [`GDrawCommandImage`](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage "GDrawCommandImage") in `init()`, before calling `window_stack_push()`: ``` // Create the object from resource files\_command\_image = gdraw\_command\_image\_create\_with\_resource(RESOURCE\_ID\_EXAMPLE\_IMAGE); ``` Next, define the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") that will be used to draw the PDC image: ``` static void update\_proc(Layer \*layer, GContext \*ctx) {// Set the origin offset from the context for drawing the imageGPoint origin = GPoint(10, 20);// Draw the GDrawCommandImage to the GContextgdraw\_command\_image\_draw(ctx, s\_command\_image, origin);} ``` Next, create a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") to display the image: ``` static Layer \*s\_canvas\_layer; ``` Assign the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") that will do the rendering to the canvas [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") and add it to the desired [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") during `window_load()`: ``` // Create the canvas Layers\_canvas\_layer = layer\_create(GRect(30, 30, bounds.size.w, bounds.size.h));// Set the LayerUpdateProclayer\_set\_update\_proc(s\_canvas\_layer, update\_proc);// Add to parent Windowlayer\_add\_child(window\_layer, s\_canvas\_layer); ``` Finally, don't forget to free the memory used by the sub-components of the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") in `main_window_unload()`: ``` // Destroy the canvas Layerlayer\_destroy(s\_canvas\_layer);// Destroy the PDC imagegdraw\_command\_image\_destroy(s\_command\_image); ``` When run, the PDC image will be loaded, and rendered in the [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"). To put the image into contrast, optionally change the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") background color after `window_create()`: ``` window\_set\_background\_color(s\_main\_window, GColorBlueMoon); ``` The result will look similar to the example shown below. ![weather-image](https://developer.repebble.com/assets/images/tutorials/advanced/weather-image.png) --- # App Configuration Source: https://developer.repebble.com/guides/user-interfaces/app-configuration/ Many watchfaces and watchapps in the Pebble appstore include the ability to customize their behavior or appearance through the use of a configuration page. [Clay for Pebble](https://github.com/pebble-dev/clay) is the recommended approach for creating configuration pages, and is what will be covered in this guide. If you need to host your own configuration pages, please follow our [_Manual Setup_](https://developer.repebble.com/guides/user-interfaces/app-configuration-static.md) guide. ![Clay Sample](https://developer.repebble.com/assets/images/guides/user-interfaces/app-configuration/clay-sample.png) Clay for Pebble dramatically simplifies the process of creating a configuration page, by allowing developers to define their application settings using a simple [JSON](https://en.wikipedia.org/wiki/JSON) file. Clay processes the JSON file and then dynamically generates a configuration page which matches the existing style of the Pebble mobile application, and it even works without an Internet connection. ## Enabling Configuration For an app to be configurable, it must include the 'configurable' item in `package.json`. ``` "capabilities": ["configurable"] ``` The presence of this value tells the mobile app to display the gear icon that is associated with the ability to launch the config page next to the app itself. ## Installing Clay Clay is available as a [_Pebble Package_](https://developer.repebble.com/guides/pebble-packages.md), so it takes minimal effort to install. Within your project folder, just type: ``` $ pebble package install @rebble/clay ``` ## Choosing messageKeys When passing data between the configuration page and the watch application, we define `messageKeys` to help us easily identify the different values. In this example, we're going to allow users to control the background color, foreground color, whether the watchface ticks on seconds and whether any animations are displayed. We define `messageKeys` in the `package.json` file for each configuration setting in our application: ``` "messageKeys": ["BackgroundColor","ForegroundColor","SecondTick","Animations"] ``` ## Creating the Clay Configuration Create the Clay configuration file as `src/pkjs/config.js`. It is not part of the project template, so `pebble new-project` and CloudPebble do not create it. It defines each type of HTML form entity that is required. These types include: - [Section](https://github.com/pebble-dev/clay#section) - [Heading](https://github.com/pebble-dev/clay#heading) - [Text](https://github.com/pebble-dev/clay#text) - [Input](https://github.com/pebble-dev/clay#input) - [Toggle](https://github.com/pebble-dev/clay#toggle) - [Select](https://github.com/pebble-dev/clay#select) - [Color Picker](https://github.com/pebble-dev/clay#color-picker) - [Radio Group](https://github.com/pebble-dev/clay#radio-group) - [Checkbox Group](https://github.com/pebble-dev/clay#checkbox-group) - [Generic Button](https://github.com/pebble-dev/clay#generic-button) - [Range Slider](https://github.com/pebble-dev/clay#range-slider) - [Submit Button](https://github.com/pebble-dev/clay#submit) In our example configuration page, we will add some introductory text, and group our fields into two sections. All configuration pages must have a submit button at the end, which is used to send the JSON data back to the watch. ![Clay](https://developer.repebble.com/assets/images/guides/user-interfaces/app-configuration/clay-actual.png) Now start populating the configuration file with the sections you require, then add the required elements to each section. Be sure to assign the correct `messageKey` to each field. ``` module.exports = [{"type": "heading","defaultValue": "App Configuration"},{"type": "text","defaultValue": "Here is some introductory text."},{"type": "section","items": [{"type": "heading","defaultValue": "Colors"},{"type": "color","messageKey": "BackgroundColor","defaultValue": "0x000000","label": "Background Color"},{"type": "color","messageKey": "ForegroundColor","defaultValue": "0xFFFFFF","label": "Foreground Color"}]},{"type": "section","items": [{"type": "heading","defaultValue": "More Settings"},{"type": "toggle","messageKey": "SecondTick","label": "Enable Seconds","defaultValue": false},{"type": "toggle","messageKey": "Animations","label": "Enable Animations","defaultValue": false}]},{"type": "submit","defaultValue": "Save Settings"}]; ``` ## Initializing Clay To initialize Clay, all you need to do is add the following JavaScript into your `index.js` file. ``` // Import the Clay packagevar Clay = require('@rebble/clay');// Load our Clay configuration filevar clayConfig = require('./config');// Initialize Clayvar clay = new Clay(clayConfig); ``` > When using the local SDK, it is possible to use a pure JSON configuration file (`config.json`). If this is the case, you must not include the `module.exports = []` in your configuration file, and you need to `var clayConfig = require('./config.json');` ## Receiving Config Data Within our watchapp we need to open a connection with [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") to begin listening for data from Clay, and also provide a handler to process the data once it has been received. ``` void prv\_init(void) {// ...// Open AppMessage connectionapp\_message\_register\_inbox\_received(prv\_inbox\_received\_handler);app\_message\_open(128, 128);// ...} ``` Once triggered, our handler will receive a [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") containing [`Tuple`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple "Tuple") objects for each `messageKey`. Note that the key names need to be prefixed with `MESSAGE_KEY_`. ``` static void prv\_inbox\_received\_handler(DictionaryIterator \*iter, void \*context) {// Read color preferencesTuple \*bg\_color\_t = dict\_find(iter, MESSAGE\_KEY\_BackgroundColor);if(bg\_color\_t) {GColor bg\_color = GColorFromHEX(bg\_color\_t-\>value-\>int32);}Tuple \*fg\_color\_t = dict\_find(iter, MESSAGE\_KEY\_ForegroundColor);if(fg\_color\_t) {GColor fg\_color = GColorFromHEX(fg\_color\_t-\>value-\>int32);}// Read boolean preferencesTuple \*second\_tick\_t = dict\_find(iter, MESSAGE\_KEY\_SecondTick);if(second\_tick\_t) {bool second\_ticks = second\_tick\_t-\>value-\>int32 == 1;}Tuple \*animations\_t = dict\_find(iter, MESSAGE\_KEY\_Animations);if(animations\_t) {bool animations = animations\_t-\>value-\>int32 == 1;}} ``` ## Persisting Settings By default, Clay will persist your settings in localStorage within the mobile application. It is common practice to also save settings within the persistent storage on the watch. This creates a seamless experience for users launching your application, as their settings can be applied on startup. This means there isn't an initial delay while the settings are loaded from the phone. You could save each individual value within the persistent storage, or you could create a struct to hold all of your settings, and save that entire object. This has the benefit of simplicity, and because writing to persistent storage is slow, it also provides improved performance. ``` // Persistent storage key#define SETTINGS\_KEY 1// Define our settings structtypedef struct ClaySettings {GColor BackgroundColor;GColor ForegroundColor;bool SecondTick;bool Animations;} ClaySettings;// An instance of the structstatic ClaySettings settings;// AppMessage receive handlerstatic void prv\_inbox\_received\_handler(DictionaryIterator \*iter, void \*context) {// Assign the values to our structTuple \*bg\_color\_t = dict\_find(iter, MESSAGE\_KEY\_BackgroundColor);if (bg\_color\_t) {settings.BackgroundColor = GColorFromHEX(bg\_color\_t-\>value-\>int32);}// ...prv\_save\_settings();}// Save the settings to persistent storagestatic void prv\_save\_settings() {persist\_write\_data(SETTINGS\_KEY, &settings, sizeof(settings));} ``` You can see a complete implementation of persisting a settings struct in the [Pebble Clay Example](https://github.com/pebble-examples/clay-example). ## What's Next If you're thinking that Clay won't be as flexible as hand crafting your own configuration pages, you're mistaken. Developers can extend the functionality of Clay in a number of ways: - Define a [custom function](https://github.com/pebble-dev/clay#custom-function) to enhance the interactivity of the page. - [Override events](https://github.com/pebble-dev/clay#handling-the-showconfiguration-and-webviewclosed-events-manually) and transform the format of the data before it's transferred to the watch. - Create and share your own [custom components](https://github.com/pebble-dev/clay#custom-components). Why not find out more about [Clay for Pebble](https://github.com/pebble-dev/clay) and perhaps even [contribute](https://github.com/pebble-dev/clay/blob/master/CONTRIBUTING.md) to the project, it's open source! --- # App Exit Reason Source: https://developer.repebble.com/guides/user-interfaces/app-exit-reason/ Introduced in SDK v4.0, the [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") API allows developers to provide a reason when terminating their application. The system uses these reasons to determine where the user should be sent when the current application terminates. At present, there are only two [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") states when exiting an application, but this may change in future updates. ### APP\_EXIT\_NOT\_SPECIFIED This is the default state and when the current watchapp terminates. The user is returned to their previous location. If you do not specify an [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason"), this state will be used automatically. ``` static void prv\_deinit() {// Optional, default behavior// App will exit to the previous location in the systemexit\_reason\_set(APP\_EXIT\_NOT\_SPECIFIED);} ``` ### APP\_EXIT\_ACTION\_PERFORMED\_SUCCESSFULLY This state is primarily provided for developers who are creating one click action applications. When the current watchapp terminates, the user is returned to the default watchface. ``` static void prv\_deinit() {// App will exit to default watchfaceexit\_reason\_set(APP\_EXIT\_ACTION\_PERFORMED\_SUCCESSFULLY);} ``` --- # AppGlance C API Source: https://developer.repebble.com/guides/user-interfaces/appglance-c/ An app's "glance" is the visual representation of a watchapp in the launcher and provides glanceable information to the user. The [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") API, added in SDK 4.0, enables developers to programmatically set the icon and subtitle that appears alongside their app in the launcher. > The [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") API is only applicable to watchapps, it is not supported by watchfaces. ## Glances and AppGlanceSlices An app's glance can change over time, and is defined by zero or more [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") each consisting of a layout (including a subtitle and icon), as well as an expiration time. AppGlanceSlices are displayed in the order they were added, and will persist until their expiration time, or another call to [`app_glance_reload()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload"). > To create an [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") with no expiration time, use [`APP_GLANCE_SLICE_NO_EXPIRATION`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_NO_EXPIRATION "APP\_GLANCE\_SLICE\_NO\_EXPIRATION") Developers can change their watchapp’s glance by calling the [`app_glance_reload()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload") method, which first clears any existing app glance slices, and then loads zero or more [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") as specified by the developer. The [`app_glance_reload()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload") method is invoked with two parameters: a pointer to an [`AppGlanceReloadCallback`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback "AppGlanceReloadCallback") that will be invoked after the existing app glance slices have been cleared, and a pointer to context data. Developers can add new [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") to their app's glance in the [`AppGlanceReloadCallback`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback "AppGlanceReloadCallback"). ``` // ...// app\_glance\_reload callbackstatic void prv\_update\_app\_glance(AppGlanceReloadSession \*session,size\_t limit, void \*context) {// Create and add app glance slices...}static void prv\_deinit() {// deinit code// ...// Reload the watchapp's app glanceapp\_glance\_reload(prv\_update\_app\_glance, NULL);} ``` ## The app\_glance\_reload Callback The [`app_glance_reload()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload") is invoked with 3 parameters, a pointer to an [`AppGlanceReloadSession`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadSession "AppGlanceReloadSession") (which is used when invoking [`app_glance_add_slice()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice "app\_glance\_add\_slice")) , the maximum number of slices you are able to add (as determined by the system at run time), and a pointer to the context data that was passed into [`app_glance_reload()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload"). The context data should contain all the information required to build the [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice"), and is typically cast to a specific type before being used. > The `limit` is currently set to 8 app glance slices per watchapp, though there is no guarantee that this value will remain static, and developers should always ensure they are not adding more slices than the limit. ![Hello World](https://developer.repebble.com/assets/images/guides/appglance-c/hello-world-app-glance.png) In this example, we’re passing the string we would like to set as the subtitle, by using the context parameter. The full code for this example can be found in the [AppGlance-Hello-World](https://github.com/pebble-examples/app-glance-hello-world) repository. ``` static void prv\_update\_app\_glance(AppGlanceReloadSession \*session,size\_t limit, void \*context) {// This should never happen, but developers should always ensure they are// not adding more slices than are availableif (limit \< 1) return;// Cast the context object to a stringconst char \*message = context;// Create the AppGlanceSlice// NOTE: When .icon is not set, the app's default icon is usedconst AppGlanceSlice entry = (AppGlanceSlice) {.layout = {.icon = APP\_GLANCE\_SLICE\_DEFAULT\_ICON,.subtitle\_template\_string = message},.expiration\_time = APP\_GLANCE\_SLICE\_NO\_EXPIRATION};// Add the slice, and check the resultconst AppGlanceResult result = app\_glance\_add\_slice(session, entry);if (result != APP\_GLANCE\_RESULT\_SUCCESS) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "AppGlance Error: %d", result);}} ``` The `layout` member is an [`AppGlanceSliceLayout`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSliceLayout "AppGlanceSliceLayout"). Its `icon` is the published resource ID of the bitmap to show, or [`APP_GLANCE_SLICE_DEFAULT_ICON`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_DEFAULT_ICON "APP\_GLANCE\_SLICE\_DEFAULT\_ICON") for the app's own icon, and its `subtitle_template_string` is the text shown under the app name, or `NULL` for no text. > **NOTE:** When an [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") is loaded with the [`app_glance_add_slice()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice "app\_glance\_add\_slice") method, the slice's `layout.subtitle_template_string` is copied to the app's glance, meaning the string does not need to persist after the call to [`app_glance_add_slice()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice "app\_glance\_add\_slice") is made. ## Using Custom Icons In order to use custom icons within an [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice"), you need to use the new `publishedMedia` entry in the `package.json` file. - Create your images as 25px x 25px PNG files. - Add your images as media resources in the `package.json`. - Then add the `publishedMedia` declaration. You should end up with something like this: ``` "resources": {"media": [{"name": "WEATHER\_HOT\_ICON\_TINY","type": "bitmap","file": "hot\_tiny.png"}],"publishedMedia": [{"name": "WEATHER\_HOT","id": 1,"glance": "WEATHER\_HOT\_ICON\_TINY"}]} ``` Then you can reference the `icon` by `name` in your [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice"). You must use the prefix `PUBLISHED_ID_`. E.g. `PUBLISHED_ID_WEATHER_HOT`. ## Subtitle Template Strings The `subtitle_template_string` field provides developers with a string formatting language for app glance subtitles. Developers can create a single app glance slice which updates automatically based upon a timestamp. For example, the template can be used to create a countdown until a timestamp (`time_until`), or the duration since a timestamp (`time_since`). The result from the timestamp evaluation can be output in various different time-format's, such as: - It's 50 days until New Year - Your Uber will arrive in 5 minutes - You are 15515 days old ### Template Structure The template string has the following structure: `{evaluation(timestamp)|format(parameters)}` Let's take a look at a simple countdown example: `Your Uber will arrive in 1 hr 10 min 4 sec` In this example, we need to know the time until our timestamp: `time_until(1467834606)`, then output the duration using an abbreviated time-format: `%aT`. `Your Uber will arrive in {time_until(1467834606)|format('%aT')}` ### Format Parameters Each format parameter is comprised of an optional predicate, and a time-format, separated by a colon. The time-format parameter is only output if the predicate evaluates to true. If a predicate is not supplied, the time-format is output by default. `format(predicate:'time-format')` #### Predicate The predicates are composed of a comparator and time value. For example, the difference between `now` and the timestamp evaluation is: - `>1d` Greater than 1 day - `<12m` Less than 12 months - `>=6m` Greater than or equal to 6 months - `<=1d12h` Less than or equal to 1 day, 12 hours. The supported time units are: - `d` (Day) - `H` (Hour) - `M` (Minute) - `S` (Second) #### Time Format The time-format is a single quoted string, comprised of a percent sign and an optional format flag, followed by a time unit. For example: `'%aT'` Abbreviated time. e.g. 1 hr 10 min 4 sec The optional format flags are: - `a` Adds abbreviated units (translated and with proper pluralization) (overrides 'u' flag) - `u` Adds units (translated and with proper pluralization) (overrides 'a' flag) - `-` Negates the input for this format specifier - `0` Pad value to the "expected" number of digits with zeros - `f` Do not modulus the value The following table demonstrates sample output for each time unit, and the effects of the format flags. | Time Unit | No flag | 'u' flag | 'a' flag | '0' flag | 'f' flag | | --- | --- | --- | --- | --- | --- | | **y** | \ | \ year(s) | \ yr(s) | \ | \ | | output: | 4 | 4 years | 4 yr | 04 | 4 | | **m** | \ | \ month(s) | \ mo(s) | \ | \ | | output: | 8 | 8 months | 8 mo | 08 | 16 | | **d** | \ | \ days | \ d | \ | \ | | output: | 7 | 7 days | 7 d | 07 | 38 | | **H** | \ | \ hour(s) | \ hr | \ | \ | | output: | 1 | 1 hour | 1 hr | 01 | 25 | | **M** | \ | \ minute(s) | \ min | \ | \ | | output: | 22 | 22 minutes | 22 min | 22 | 82 | | **S** | \ | \ second(s) | \ sec | \ | \ | | output: | 5 | 5 seconds | 5 sec | 05 | 65 | | **T** | %H:%0M:%0S (if \>= 1hr) * * * %M:%0S (if \>= 1m) * * * %S (otherwise) | %uH, %uM, and %uS * * * %uM, and %uS * * * %uS | %aH %aM %aS * * * %aM %aS * * * %aS | %0H:%0M:%0S (always) | %fH:%0M:%0S * * * %M:%0S * * * %S | | output: | 1:53:20 * * * 53:20 * * * 20 | 1 hour, 53 minutes, and 20 seconds * * * 53 minutes, and 20 seconds * * * 20 seconds | 1 hr 53 min 20 sec * * * 53 min 20 sec * * * 20 sec | 01:53:20 * * * 00:53:20 * * * 00:00:20 | 25:53:20 * * * 53:20 * * * 20 | | **R** | %H:%0M (if \>= 1hr) * * * %M (otherwise) | %uH, and %uM * * * %uM | %aH %aM * * * %aM | %0H:%0M (always) | %fH:%0M * * * %M | | output: | 23:04 * * * 15 | 23 hours, and 4 minutes * * * 15 minutes | 23 hr 4 min * * * 15 min | 23:04 * * * 00:15 | 47:04 * * * 15 | > Note: The time units listed above are not all available for use as predicates, but can be used with format flags. #### Advanced Usage We've seen how to use a single parameter to generate our output, but for more advanced cases, we can chain multiple parameters together. This allows for a single app glance slice to produce different output as each parameter evaluates successfully, from left to right. `format(predicate:'time-format', predicate:'time-format', predicate:'time-format')` For example, we can generate a countdown which displays different output before, during and after the event: - 100 days left - 10 hr 5 min 20 sec left - It's New Year! - 10 days since New Year To produce this output we could use the following template: `{time_until(1483228800)|format(>=1d:'%ud left',>0S:'%aT left',>-1d:\"It's New Year!\", '%-ud since New Year')}` ## Adding Multiple Slices An app's glance can change over time, with the slices being displayed in the order they were added, and removed after the `expiration_time`. In order to add multiple app glance slices, we simply need to create and add multiple [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice") instances, with increasing expiration times. ![Virtual Pet](https://developer.repebble.com/assets/images/guides/appglance-c/virtual-pet-app-glance.png) In the following example, we create a basic virtual pet that needs to be fed (by opening the app) every 12 hours, or else it runs away. When the app closes, we update the app glance to display a new message and icon every 3 hours until the virtual pet runs away. The full code for this example can be found in the [AppGlance-Virtual-Pet](https://github.com/pebble-examples/app-glance-virtual-pet) repository. ``` // How often pet needs to be fed (12 hrs)#define PET\_FEEDING\_FREQUENCY 3600\*12// Number of states to show in the launcher#define NUM\_STATES 4// Icons associated with each stateconst uint32\_t icons[NUM\_STATES] = {PUBLISHED\_ID\_ICON\_FROG\_HAPPY,PUBLISHED\_ID\_ICON\_FROG\_HUNGRY,PUBLISHED\_ID\_ICON\_FROG\_VERY\_HUNGRY,PUBLISHED\_ID\_ICON\_FROG\_MISSING};// Message associated with each stateconst char \*messages[NUM\_STATES] = {"Mmm, that was delicious!!","I'm getting hungry..","I'm so hungry!! Please feed me soon..","Your pet ran away :("};static void prv\_update\_app\_glance(AppGlanceReloadSession \*session,size\_t limit, void \*context) {// Ensure we have sufficient slicesif (limit \< NUM\_STATES) {APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Error: app needs %d slices (%zu available)",NUM\_STATES, limit);}time\_t expiration\_time = time(NULL);// Build and add NUM\_STATES slicesfor (int i = 0; i \< NUM\_STATES; i++) {// Increment the expiration\_time of the slice on each passexpiration\_time += PET\_FEEDING\_FREQUENCY / NUM\_STATES;// Set it so the last slice never expiresif (i == (NUM\_STATES - 1)) expiration\_time = APP\_GLANCE\_SLICE\_NO\_EXPIRATION;// Create the sliceconst AppGlanceSlice slice = {.layout = {.icon = icons[i],.subtitle\_template\_string = messages[i]},.expiration\_time = expiration\_time};// add the slice, and check the resultAppGlanceResult result = app\_glance\_add\_slice(session, slice);if (result != APP\_GLANCE\_RESULT\_SUCCESS) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Error adding AppGlanceSlice: %d", result);}}}static void prv\_deinit() {app\_glance\_reload(prv\_update\_app\_glance, NULL);}void main() {app\_event\_loop();prv\_deinit();} ``` --- # AppGlance in PebbleKit JS Source: https://developer.repebble.com/guides/user-interfaces/appglance-pebblekit-js/ > Note: `Pebble.appGlanceReload()` is not supported in the Pebble mobile app and its callbacks never fire. Use the [_C AppGlance API_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) from the watchapp instead. The [_AppGlance C API_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) guide describes how to set the icon and subtitle shown alongside an app in the launcher with [`app_glance_reload()`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload "app\_glance\_reload"), including the slice format, system and published media icons, and the subtitle template string language. --- # Content Size Source: https://developer.repebble.com/guides/user-interfaces/content-size/ The [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) API, added in SDK 4.2, allows developers to dynamically adapt their watchface and watchapp design based upon the system `Text Size` preference (_Settings \> Notifications \> Text Size_). While this allows developers to create highly accessible designs, it also serves to provide a mechanism for creating designs which are less focused upon screen size, and more focused upon content size. ![ContentSize](https://developer.repebble.com/assets/images/guides/content-size/anim.gif) The `Text Size` setting displays the following options on all platforms: - Small - Medium - Large Whereas, the [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) API will return different content sizes based on the `Text Size` setting, varying by platform. The list of content sizes is: - Small - Medium - Large - Extra Large An example of the varying content sizes: - `Text Size`: `small` on `Basalt` is `ContentSize`: `small` - `Text Size`: `small` on `Emery` is `ContentSize`: `medium` The following table describes the relationship between `Text Size`, `Platform` and `ContentSize`: | Platform | Text Size: Small | Text Size: Medium | Text Size: Large | | --- | --- | --- | --- | | Aplite, Basalt, Chalk, Diorite, Flint | ContentSize: Small | ContentSize: Medium | ContentSize: Large | | Emery, Gabbro | ContentSize: Medium | ContentSize: Large | ContentSize: Extra Large | > _At present the Text Size setting only affects notifications and some system UI components, but other system UI components will be updated to support ContentSize in future versions._ We highly recommend that developers begin to build and update their applications with consideration for [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) to provide the best experience to users. ## Detecting ContentSize In order to detect the current [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) developers can use the [`preferred_content_size()`](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size "preferred\_content\_size") function. The [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) will never change during runtime, so it's perfectly acceptable to check this once during `init()`. ``` static PreferredContentSize s\_content\_size;void init() {s\_content\_size = preferred\_content\_size();// ...} ``` ## Adapting Layouts There are a number of different approaches to adapting the screen layout based upon content size. You could change font sizes, show or hide design elements, or even present an entirely different UI. In the following example, we will change font sizes based on the [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) ``` static TextLayer \*s\_text\_layer;static PreferredContentSize s\_content\_size;void init() {s\_content\_size = preferred\_content\_size();// ...switch (s\_content\_size) {case PreferredContentSizeMedium:// Use a medium fonttext\_layer\_set\_font(s\_text\_layer, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_18\_BOLD));break;case PreferredContentSizeLarge:case PreferredContentSizeExtraLarge:// Use a large fonttext\_layer\_set\_font(s\_text\_layer, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_28\_BOLD));break;default:// Use a small fonttext\_layer\_set\_font(s\_text\_layer, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_14\_BOLD));break;}// ...} ``` ## Additional Considerations When developing an application which dynamically adjusts based on the [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) setting, try to avoid using fixed widths and heights. Calculate coordinates and dimensions based upon the size of the root layer, [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") and [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) --- # Layers Source: https://developer.repebble.com/guides/user-interfaces/layers/ The [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") and associated subclasses (such as [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") and [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer")) form the foundation of the UI for every Pebble watchapp or watchface, and are added to a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") to construct the UI's design. Each [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") type contains at least three basic elements: - Frame - contains the position and dimensions of the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), relative to the parent object. - Bounds - contains the drawable bounding box within the frame. This allows only a portion of the layer to be visible, and is relative to the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") frame. - Update procedure - the function that performs the drawing whenever the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") is rendered. The subclasses implement a convenience update procedure with additional data to achieve their specialization. ## Layer Hierarchy Every app must consist of at least one [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") in order to successfully launch. Mutiple [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") objects are added as children of the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"), which itself contains a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") known as the 'root layer'. When the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is rendered, each child [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") is rendered in the order in which they were added. For example: ``` static Window \*s\_main\_window;static BitmapLayer \*s\_background\_layer;static TextLayer \*s\_time\_layer; ``` ``` // Get the Window's root layerLayer \*root\_layer = window\_get\_root\_layer(s\_main\_window);/\* set up BitmapLayer and TextLayer \*/// Add the background layer first, so that it is drawn behind the timelayer\_add\_child(root\_layer, bitmap\_layer\_get\_layer(s\_background\_layer));// Add the time layer secondlayer\_add\_child(root\_layer, text\_layer\_get\_layer(s\_time\_layer)); ``` Once added to a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"), the ordering of each [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") cannot be modified, but one can be placed at the front by removing and re-adding it to the hierarchy: ``` // Bring a layer to the frontlayer\_remove\_from\_parent(s\_some\_layer);layer\_add\_child(root\_layer, s\_some\_layer); ``` ## Update Procedures For creating custom drawing implementations, the basic [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") update procedure can be reassigned to one created by a developer. This takes the form of a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"), and provides a [`GContext`](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md "Graphics Context") object which can be used for drawing primitive shapes, paths, text, and images. > Note: See [_Graphics and Animations_](https://developer.repebble.com/guides/graphics-and-animations.md) for more information on drawing with the graphics context. ``` static void layer\_update\_proc(Layer \*layer, GContext \*ctx) {// Custom drawing happens here} ``` This function must then be assigned to the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") that will be drawn with it: ``` // Set this Layer's update procedurelayer\_set\_update\_proc(s\_some\_layer, layer\_update\_proc); ``` The update procedure will be called every time the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") must be redrawn. This is typically when any other [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") requests a redraw, the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is shown/hidden, the hierarchy changes, or a modal (such as a notification) appears. The [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") can also be manually marked as 'dirty', and will be redrawn at the next opportunity (usually immediately): ``` // Request a redrawlayer\_mark\_dirty(s\_some\_layer); ``` ## Layer Subclasses For convenience, there are multiple subclasses of [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") included in the Pebble SDK to allow developers to easily construct their app's UI. Each should be created when the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is loading (using the `.load` [`WindowHandler`](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler "WindowHandler")) and destroyed when it is unloading (using the `.unload` [`WindowHandler`](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler "WindowHandler")). These are briefly outlined below, alongside a simple usage example split into three code snippets - the element declarations, the setup procedure, and the teardown procedure. ### TextLayer The [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is the most commonly used subclass of [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), and allows apps to render text using any available font, with built-in behavior to handle text color, line wrapping, alignment, etc. ``` static TextLayer \*s\_text\_layer; ``` ``` // Create a TextLayers\_text\_layer = text\_layer\_create(bounds);// Set some propertiestext\_layer\_set\_text\_color(s\_text\_layer, GColorWhite);text\_layer\_set\_background\_color(s\_text\_layer, GColorBlack);text\_layer\_set\_overflow\_mode(s\_text\_layer, GTextOverflowModeWordWrap);text\_layer\_set\_alignment(s\_text\_layer, GTextAlignmentCenter);// Set the text showntext\_layer\_set\_text(s\_text\_layer, "Hello, World!");// Add to the Windowlayer\_add\_child(root\_layer, text\_layer\_get\_layer(s\_text\_layer)); ``` ``` // Destroy the TextLayertext\_layer\_destroy(s\_text\_layer); ``` ### BitmapLayer The [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") provides an easy way to show images loaded into [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") objects from an image resource. Images shown using a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") are automatically centered within the bounds provided to [`bitmap_layer_create()`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_create "bitmap\_layer\_create"). Read [_Images_](https://developer.repebble.com/guides/app-resources/images.md) to learn more about using image resources in apps. > Note: PNG images with transparency should use `bitmap` resource type, and use the [`GCompOpSet`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpSet "GCompOpSet") compositing mode when being displayed, as shown below. ``` static BitmapLayer \*s\_bitmap\_layer;static GBitmap \*s\_bitmap; ``` ``` // Load the images\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_EXAMPLE\_IMAGE);// Create a BitmapLayers\_bitmap\_layer = bitmap\_layer\_create(bounds);// Set the bitmap and compositing modebitmap\_layer\_set\_bitmap(s\_bitmap\_layer, s\_bitmap);bitmap\_layer\_set\_compositing\_mode(s\_bitmap\_layer, GCompOpSet);// Add to the Windowlayer\_add\_child(root\_layer, bitmap\_layer\_get\_layer(s\_bitmap\_layer)); ``` ``` // Destroy the BitmapLayerbitmap\_layer\_destroy(s\_bitmap\_layer); ``` ### StatusBarLayer If a user needs to see the current time inside an app (instead of exiting to the watchface), the [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer") component can be used to display this information at the top of the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). Colors and separator display style can be customized. ``` static StatusBarLayer \*s\_status\_bar; ``` ``` // Create the StatusBarLayers\_status\_bar = status\_bar\_layer\_create();// Set propertiesstatus\_bar\_layer\_set\_colors(s\_status\_bar, GColorBlack, GColorBlueMoon);status\_bar\_layer\_set\_separator\_mode(s\_status\_bar, StatusBarLayerSeparatorModeDotted);// Add to Windowlayer\_add\_child(root\_layer, status\_bar\_layer\_get\_layer(s\_status\_bar)); ``` ``` // Destroy the StatusBarLayerstatus\_bar\_layer\_destroy(s\_status\_bar); ``` ### MenuLayer The [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") allows the user to scroll a list of options using the Up and Down buttons, and select an option to trigger an action using the Select button. It differs from the other [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") subclasses in that it makes use of a number of [`MenuLayerCallbacks`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks "MenuLayerCallbacks") to allow the developer to fully control how it renders and behaves. Some minimum example callbacks are shown below: ``` static MenuLayer \*s\_menu\_layer; ``` ``` static uint16\_t get\_num\_rows\_callback(MenuLayer \*menu\_layer, uint16\_t section\_index, void \*context) {const uint16\_t num\_rows = 5;return num\_rows;}static void draw\_row\_callback(GContext \*ctx, const Layer \*cell\_layer, MenuIndex \*cell\_index, void \*context) {static char s\_buff[16];snprintf(s\_buff, sizeof(s\_buff), "Row %d", (int)cell\_index-\>row);// Draw this row's indexmenu\_cell\_basic\_draw(ctx, cell\_layer, s\_buff, NULL, NULL);}static int16\_t get\_cell\_height\_callback(struct MenuLayer \*menu\_layer, MenuIndex \*cell\_index, void \*context) {const int16\_t cell\_height = 44;return cell\_height;}static void select\_callback(struct MenuLayer \*menu\_layer, MenuIndex \*cell\_index, void \*context) {// Do something in response to the button press} ``` ``` // Create the MenuLayers\_menu\_layer = menu\_layer\_create(bounds);// Let it receive click eventsmenu\_layer\_set\_click\_config\_onto\_window(s\_menu\_layer, window);// Set the callbacks for behavior and renderingmenu\_layer\_set\_callbacks(s\_menu\_layer, NULL, (MenuLayerCallbacks) {.get\_num\_rows = get\_num\_rows\_callback,.draw\_row = draw\_row\_callback,.get\_cell\_height = get\_cell\_height\_callback,.select\_click = select\_callback,});// Add to the Windowlayer\_add\_child(root\_layer, menu\_layer\_get\_layer(s\_menu\_layer)); ``` ``` // Destroy the MenuLayermenu\_layer\_destroy(s\_menu\_layer); ``` ### ScrollLayer The [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") provides an easy way to use the Up and Down buttons to scroll large content that does not all fit onto the screen at the same time. The usage of this type differs from the others in that the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") objects that are scrolled are added as children of the [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer"), which is then in turn added as a child of the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). The [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") frame is the size of the 'viewport', while the content size determines how far the user can scroll in each direction. The example below shows a [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") scrolling some long text, the total size of which is calculated with [`graphics_text_layout_get_content_size()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_layout_get_content_size "graphics\_text\_layout\_get\_content\_size") and used as the [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") content size. > Note: The scrolled [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") frame is relative to that of its parent, the [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer"). ``` static TextLayer \*s\_text\_layer;static ScrollLayer \*s\_scroll\_layer; ``` ``` GFont font = fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_28\_BOLD);// Find the bounds of the scrolling textGRect shrinking\_rect = GRect(0, 0, bounds.size.w, 2000);char \*text = "Example text that is really really really really really \ really really really really really really long";GSize text\_size = graphics\_text\_layout\_get\_content\_size(text, font, shrinking\_rect, GTextOverflowModeWordWrap, GTextAlignmentLeft);GRect text\_bounds = bounds;text\_bounds.size.h = text\_size.h;// Create the TextLayers\_text\_layer = text\_layer\_create(text\_bounds);text\_layer\_set\_overflow\_mode(s\_text\_layer, GTextOverflowModeWordWrap);text\_layer\_set\_font(s\_text\_layer, font);text\_layer\_set\_text(s\_text\_layer, text);// Create the ScrollLayers\_scroll\_layer = scroll\_layer\_create(bounds);// Set the scrolling content sizescroll\_layer\_set\_content\_size(s\_scroll\_layer, text\_size);// Let the ScrollLayer receive click eventsscroll\_layer\_set\_click\_config\_onto\_window(s\_scroll\_layer, window);// Add the TextLayer as a child of the ScrollLayerscroll\_layer\_add\_child(s\_scroll\_layer, text\_layer\_get\_layer(s\_text\_layer));// Add the ScrollLayer as a child of the Windowlayer\_add\_child(root\_layer, scroll\_layer\_get\_layer(s\_scroll\_layer)); ``` ``` // Destroy the ScrollLayer and TextLayerscroll\_layer\_destroy(s\_scroll\_layer);text\_layer\_destroy(s\_text\_layer); ``` ### ActionBarLayer The [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") allows apps to use the familiar black right-hand bar, featuring icons denoting the action that will occur when each button on the right hand side is pressed. For example, 'previous track', 'more actions', and 'next track' in the built-in Music app. For three or fewer actions, the [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") can be more appropriate than a [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") for presenting the user with a list of actionable options. Each action's icon must also be loaded into a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") object from app resources. The example below demonstrates show to set up an [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") showing an up, down, and checkmark icon for each of the buttons. ``` static ActionBarLayer \*s\_action\_bar;static GBitmap \*s\_up\_bitmap, \*s\_down\_bitmap, \*s\_check\_bitmap; ``` ``` // Load icon bitmapss\_up\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_UP\_ICON);s\_down\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_DOWN\_ICON);s\_check\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_CHECK\_ICON);// Create ActionBarLayers\_action\_bar = action\_bar\_layer\_create();action\_bar\_layer\_set\_click\_config\_provider(s\_action\_bar, click\_config\_provider);// Set the iconsaction\_bar\_layer\_set\_icon(s\_action\_bar, BUTTON\_ID\_UP, s\_up\_bitmap);action\_bar\_layer\_set\_icon(s\_action\_bar, BUTTON\_ID\_DOWN, s\_down\_bitmap);action\_bar\_layer\_set\_icon(s\_action\_bar, BUTTON\_ID\_SELECT, s\_check\_bitmap);// Add to Windowaction\_bar\_layer\_add\_to\_window(s\_action\_bar, window); ``` ``` // Destroy the ActionBarLayeraction\_bar\_layer\_destroy(s\_action\_bar);// Destroy the icon GBitmapsgbitmap\_destroy(s\_up\_bitmap);gbitmap\_destroy(s\_down\_bitmap);gbitmap\_destroy(s\_check\_bitmap); ``` --- # Round App UI Source: https://developer.repebble.com/guides/user-interfaces/round-app-ui/ **PLATFORM NOTICE** This guide only applies to apps built to run on the Chalk and Gabbro platforms (Pebble Time Round and Pebble Round 2). > This guide is about creating round apps in code. For advice on designing a round app, read [_Round App Design_](https://developer.repebble.com/guides/design-and-interaction/in-the-round.md). With the addition of Pebble Time Round (the Chalk platform) to the Pebble family, developers face a new challenge - circular apps! With this display shape, traditional layouts will not display properly due to the obscuring of the corners. Another potential issue is the increased display resolution. Any UI elements that were not previously centered correctly (or drawn with hardcoded coordinates) will also display incorrectly. Pebble Round 2 (the Gabbro platform) is also round, with a 260 x 260 display instead of Chalk's 180 x 180. Everything in this guide applies to both. Use the `PBL_ROUND` define and the bounds of the root layer rather than the Chalk dimensions, so that one layout serves both round watches. The [`ACTION_BAR_WIDTH`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ACTION_BAR_WIDTH "ACTION\_BAR\_WIDTH") constant is 40 pixels on both round platforms. However, the Pebble SDK provides additions and functionality to help developers cope with this way of thinking. In many cases, a round display can be an aesthetic advantage. An example of this is the traditional circular dial watchface, which has been emulated on Pebble many times, but also wastes corner space. With a round display, these watchfaces can look better than ever. ![time-dots](https://developer.repebble.com/assets/images/guides/pebble-apps/display-animations/time-dots.png) ## Detecting Display Shape The first step for any app wishing to correctly support both display shapes is to use the available compiler directives to conditionally create the UI. This can be done as shown below: ``` #if defined(PBL\_RECT)printf("This code is run on a rectangular display!");/\* Rectangular UI code \*/#elif defined(PBL\_ROUND)printf("This code is run on a round display!");/\* Round UI code \*/#endif ``` Another approach for single value selection is the [`PBL_IF_RECT_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_RECT_ELSE "PBL\_IF\_RECT\_ELSE") and [`PBL_IF_ROUND_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_ROUND_ELSE "PBL\_IF\_ROUND\_ELSE") macros, which accept two parameters for each of the respective round and rectangular cases. For example, [`PBL_IF_RECT_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_RECT_ELSE "PBL\_IF\_RECT\_ELSE") will compile the first parameter on a rectangular display, and the second one otherwise: ``` // Conditionally print out the shape of the displayprintf("This is a %s display!", PBL\_IF\_RECT\_ELSE("rectangular", "round")); ``` ## Circular Drawing In addition to the older [`graphics_draw_circle()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_circle "graphics\_draw\_circle") and [`graphics_fill_circle()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_circle "graphics\_fill\_circle") functions, the Pebble SDK for the chalk platform contains additional functions to help draw shapes better suited for a round display. These include: - [`graphics_draw_arc()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_arc "graphics\_draw\_arc") - Draws a line arc clockwise between two angles within a given [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect") area, where 0° is the top of the circle. - [`graphics_fill_radial()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_radial "graphics\_fill\_radial") - Fills a circle clockwise between two angles within a given [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect") area, with adjustable inner inset radius allowing the creation of 'doughnut-esque' shapes. - [`gpoint_from_polar()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#gpoint_from_polar "gpoint\_from\_polar") - Returns a [`GPoint`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint "GPoint") object describing a point given by a specified angle within a centered [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect"). In the Pebble SDK angles between `0` and `360` degrees are specified as values scaled between `0` and [`TRIG_MAX_ANGLE`](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_ANGLE "TRIG\_MAX\_ANGLE") to preserve accuracy and avoid floating point math. These are most commonly used when dealing with drawing circles. To help with this conversion, developers can use the [`DEG_TO_TRIGANGLE()`](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE "DEG\_TO\_TRIGANGLE") macro. An example function to draw the letter 'C' in a yellow color is shown below for use in a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"). ``` static void draw\_letter\_c(GRect bounds, GContext \*ctx) {GRect frame = grect\_inset(bounds, GEdgeInsets(30));graphics\_context\_set\_fill\_color(ctx, GColorYellow);graphics\_fill\_radial(ctx, frame, GOvalScaleModeFitCircle, 30,DEG\_TO\_TRIGANGLE(-225), DEG\_TO\_TRIGANGLE(45));} ``` This produces the expected result, drawn with a smooth antialiased filled circle arc between the specified angles. ![letter-c](https://developer.repebble.com/assets/images/guides/pebble-apps/display-animations/letter-c.png) ## Adaptive Layouts With not only a difference in display shape, but also in resolution, it is very important that an app's layout not be created using hardcoded coordinates. Consider the examples below, designed to create a child [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") to fill the size of the parent layer. ``` // Bad - only works on Aplite and Basalt rectangular displaysLayer \*layer = layer\_create(GRect(0, 0, 144, 168));// Better - uses the native display sizeGRect bounds = layer\_get\_bounds(parent\_layer);Layer \*layer = layer\_create(bounds); ``` Using this style, the child layer will always fill the parent layer, regardless of its actual dimensions. In a similar vein, when working with a round display it can be important that the layout is centered correctly. A set of layout values that are in the center of the classic 144 x 168 pixel display will not be centered when displayed on a 180 x 180 or 260 x 260 display. The undesirable effect of this can be seen in the example shown below: ![cut-corners](https://developer.repebble.com/assets/images/guides/pebble-apps/display-animations/cut-corners.png) By using the technique described above, the layout's [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect") objects can specify their `origin` and `size` as a function of the dimensions of the layer they are drawn into, solving this problem. ![centered](https://developer.repebble.com/assets/images/guides/pebble-apps/display-animations/centered.png) ## Text Flow and Pagination A chief concern when working with a circular display is the rendering of large amounts of text. As demonstrated by an animation in [_Round App Design_](https://developer.repebble.com/guides/design-and-interaction/in-the-round.md#pagination), continuous reflowing of text makes it much harder to read. A solution to this problem is to render text while flowing within the constraints of the shape of the display, and to scroll/animate it one page at a time. There are three approaches to this available to developers, which are detailed below. For full examples of each, see the [`text-flow-techniques`](https://github.com/pebble-examples/text-flow-techniques) example app. ### Using TextLayer Additions to the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") API allow text rendered within it to be automatically flowed according to the curve of the display, and paged correctly when the layer is moved or animated further. After a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is created in the usual way, text flow can then be enabled: ``` // Create TextLayerTextLayer \*s\_text\_layer = text\_layer\_create(bounds);/\* other properties set up \*/// Add to parent Windowlayer\_add\_child(window\_layer, text\_layer\_get\_layer(s\_text\_layer));// Enable paging and text flow with an inset of 5 pixelstext\_layer\_enable\_screen\_text\_flow\_and\_paging(s\_text\_layer, 5); ``` > Note: The [`text_layer_enable_screen_text_flow_and_paging()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_enable_screen_text_flow_and_paging "text\_layer\_enable\_screen\_text\_flow\_and\_paging") function must be called **after** the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is added to the view hierarchy (i.e.: after using [`layer_add_child()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_add_child "layer\_add\_child")), or else it will have no effect. An example of two [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") elements flowing their text within the constraints of the display shape is shown below: ![text-flow](https://developer.repebble.com/assets/images/guides/pebble-apps/display-animations/text-flow.png) ### Using ScrollLayer The [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") UI component also contains round-friendly functionality, allowing it to scroll its child [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") elements in pages of the same height as its frame (usually the size of the parent [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")). This allows consuming long content to be a more consistent experience, whether it is text, images, or some other kind of information. ``` // Enable ScrollLayer pagingscroll\_layer\_set\_paging(s\_scroll\_layer, true); ``` When combined with a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") as the main child layer, it becomes easy to display long pieces of textual content on a round display. The [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") can be set up to handle the reflowing of text to follow the display shape, and the [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") handles the paginated scrolling. ``` // Add the TextLayer and ScrollLayer to the view hierarchyscroll\_layer\_add\_child(s\_scroll\_layer, text\_layer\_get\_layer(s\_text\_layer));layer\_add\_child(window\_layer, scroll\_layer\_get\_layer(s\_scroll\_layer));// Set the ScrollLayer's content size to the total size of the textscroll\_layer\_set\_content\_size(s\_scroll\_layer,text\_layer\_get\_content\_size(s\_text\_layer));// Enable TextLayer text flow and pagingconst int inset\_size = 2;text\_layer\_enable\_screen\_text\_flow\_and\_paging(s\_text\_layer, inset\_size);// Enable ScrollLayer pagingscroll\_layer\_set\_paging(s\_scroll\_layer, true); ``` ### Manual Text Drawing The drawing of text into a [`Graphics Context`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md "Drawing Text") can also be performed with awareness of text flow and paging preferences. This can be used to emulate the behavior of the two previous approaches, but with more flexibility. This approach involves the use of the [`GTextAttributes`](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAttributes "GTextAttributes") object, which is given to the Graphics API to allow it to flow text and paginate when being animated. When initializing the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") that will do the drawing: ``` // Create the attributes object used for text renderingGTextAttributes \*s\_attributes = graphics\_text\_attributes\_create();// Enable text flow with an inset of 5 pixelsgraphics\_text\_attributes\_enable\_screen\_text\_flow(s\_attributes, 5);// Enable pagination with a fixed reference point and bounds, used for animatinggraphics\_text\_attributes\_enable\_paging(s\_attributes, bounds.origin, bounds); ``` When drawing some text in a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc"): ``` static void update\_proc(Layer \*layer, GContext \*ctx) {GRect bounds = layer\_get\_bounds(layer);// Calculate size of the text to be drawn with current attribute settingsGSize text\_size = graphics\_text\_layout\_get\_content\_size\_with\_attributes(s\_sample\_text, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_24\_BOLD), bounds,GTextOverflowModeWordWrap, GTextAlignmentCenter, s\_attributes);// Draw the text in this box with the current attribute settingsgraphics\_context\_set\_text\_color(ctx, GColorBlack);graphics\_draw\_text(ctx, s\_sample\_text, fonts\_get\_system\_font(FONT\_KEY\_GOTHIC\_24\_BOLD),GRect(bounds.origin.x, bounds.origin.y, text\_size.w, text\_size.h),GTextOverflowModeWordWrap, GTextAlignmentCenter, s\_attributes);} ``` Once this setup is complete, the text will display correctly when moved or scrolled via a [`PropertyAnimation`](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md "PropertyAnimation"), such as one that moves the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") that draws the text upwards, and at the same time extending its height to display subsequent pages. An example animation is shown below: ``` GRect window\_bounds = layer\_get\_bounds(window\_get\_root\_layer(s\_main\_window));const int duration\_ms = 1000;// Animate the Layer upwards, lengthening it to allow the next page to be drawnGRect start = layer\_get\_frame(s\_layer);GRect finish = GRect(start.origin.x, start.origin.y - window\_bounds.size.h,start.size.w, start.size.h \* 2);// Create and scedule the PropertyAnimationPropertyAnimation \*prop\_anim = property\_animation\_create\_layer\_frame(s\_layer, &start, &finish);Animation \*animation = property\_animation\_get\_animation(prop\_anim);animation\_set\_duration(animation, duration\_ms);animation\_schedule(animation); ``` ## Working With a Circular Framebuffer The traditional rectangular Pebble app framebuffer is a single continuous memory segment that developers could access with [`gbitmap_get_data()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data "gbitmap\_get\_data"). With a round display, Pebble saves memory by clipping sections of each line of difference between the display area and the rectangle it occupies. The resulting masking pattern looks like this: ![mask](https://developer.repebble.com/assets/images/guides/pebble-apps/display-animations/mask.png) > Download this mask by saving the PNG image above, or get it as a [Photoshop PSD layer](https://developer.repebble.com/assets/images/guides/pebble-apps/display-animations/round-mask-layer.psd). This has an important implication - the memory segment of the framebuffer can no longer be accessed using classic `y * row_width + x` formulae. Instead, developers should use the [`gbitmap_get_data_row_info()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info "gbitmap\_get\_data\_row\_info") API. Gabbro stores its framebuffer with a fixed row stride but reports the same [`GBitmapFormat8BitCircular`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular "GBitmapFormat8BitCircular") format, so the same code works on both round platforms. Read [_Framebuffer Graphics_](https://developer.repebble.com/guides/graphics-and-animations/framebuffer-graphics.md) for details. When used with a given y coordinate, this will return a [`GBitmapDataRowInfo`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapDataRowInfo "GBitmapDataRowInfo") object containing a pointer to the row's data, as well as values for the minumum and maximum visible values of x coordinate on that row. For example: ``` static void round\_update\_proc(Layer \*layer, GContext \*ctx) {// Get framebufferGBitmap \*fb = graphics\_capture\_frame\_buffer(ctx);GRect bounds = layer\_get\_bounds(layer);// Write a value to all visible pixelsfor(int y = 0; y \< bounds.size.h; y++) {// Get the min and max x values for this rowGBitmapDataRowInfo info = gbitmap\_get\_data\_row\_info(fb, y);// Iterate over visible pixels in that rowfor(int x = info.min\_x; x \< info.max\_x; x++) {// Set the pixel to blackmemset(&info.data[x], GColorBlack.argb, 1);}}// Release framebuffergraphics\_release\_frame\_buffer(ctx, fb);} ``` ## Displaying More Content When more content is available than fits on the screen at any one time, the user should be made aware using visual clues. The best way to do this is to use the [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator") UI component. ![content-indicator](https://developer.repebble.com/assets/images/guides/design-and-interaction/content-indicator.png) A [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator") can be obtained in two ways. It can be created from scratch with [`content_indicator_create()`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_create "content\_indicator\_create") and manually managed to determine when the arrows should be shown, or a built-in instance can be obtained from a [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer"), as shown below: ``` // Get the ContentIndicator from the ScrollLayers\_indicator = scroll\_layer\_get\_content\_indicator(s\_scroll\_layer); ``` In order to draw the arrows indicating more information in each direction, the [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator") must be supplied with two new [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") elements that will be used to do the drawing. These should also be added as children to the main [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") root [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") such that they are visible on top of all other [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") elements: ``` static void window\_load(Window \*window) {Layer \*window\_layer = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(window\_layer);/\* ... \*/// Create two Layers to draw the arrowss\_indicator\_up\_layer = layer\_create(GRect(0, 0, bounds.size.w, STATUS\_BAR\_LAYER\_HEIGHT));s\_indicator\_down\_layer = layer\_create(GRect(0, bounds.size.h - STATUS\_BAR\_LAYER\_HEIGHT,bounds.size.w, STATUS\_BAR\_LAYER\_HEIGHT));/\* ... \*/// Add these Layers as children after all other components to appear belowlayer\_add\_child(window\_layer, s\_indicator\_up\_layer);layer\_add\_child(window\_layer, s\_indicator\_down\_layer);} ``` Once the indicator [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") elements have been created, each of the up and down directions for conventional vertical scrolling must be configured with data to control its behavior. Aspects such as the color of the arrows and background, whether or not the arrows time out after being brought into view, and the alignment of the drawn arrow within the [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") itself are configured with a `const` [`ContentIndicatorConfig`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorConfig "ContentIndicatorConfig") object when each direction is being configured: ``` // Configure the properties of each indicatorconst ContentIndicatorConfig up\_config = (ContentIndicatorConfig) {.layer = s\_indicator\_up\_layer,.times\_out = false,.alignment = GAlignCenter,.colors = {.foreground = GColorBlack,.background = GColorWhite}};content\_indicator\_configure\_direction(s\_indicator, ContentIndicatorDirectionUp,&up\_config);const ContentIndicatorConfig down\_config = (ContentIndicatorConfig) {.layer = s\_indicator\_down\_layer,.times\_out = false,.alignment = GAlignCenter,.colors = {.foreground = GColorBlack,.background = GColorWhite}};content\_indicator\_configure\_direction(s\_indicator, ContentIndicatorDirectionDown,&down\_config); ``` Unless the [`ContentIndicator`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator "ContentIndicator") has been retrieved from another [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") type that includes an instance, it should be destroyed along with its parent [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"): ``` // Destroy a manually created ContentIndicatorcontent\_indicator\_destroy(s\_indicator); ``` For layouts that use the [`StatusBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md "StatusBarLayer"), the [`ContentIndicatorDirectionUp`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorDirectionUp "ContentIndicatorDirectionUp")`.layer` in the [`ContentIndicatorConfig`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorConfig "ContentIndicatorConfig") object can be given the status bar's [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") with [`status_bar_layer_get_layer()`](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_get_layer "status\_bar\_layer\_get\_layer"), and the drawing routines for each will be managed automatically. --- # Unobstructed Area Source: https://developer.repebble.com/guides/user-interfaces/unobstructed-area/ The [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API, added in SDK 4.0, allows developers to dynamically adapt their watchface design when an area of the screen is partially obstructed by a system overlay. Currently, the Timeline Quick View feature is the only system overlay. Developers are not required to adjust their designs to cater for such system overlays, but by using the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API they can detect changes to the available screen real-estate and then move, scale, or hide their layers to achieve an optimal layout while the screen is partially obscured. ![Unobstructed-watchfaces](https://developer.repebble.com/assets/images/guides/user-interfaces/unobstructed-area/01-unobstructed-watchfaces.jpg) Sample watchfaces with Timeline Quick View overlay ![Obstructed-watchfaces](https://developer.repebble.com/assets/images/guides/user-interfaces/unobstructed-area/02-obstructed-watchfaces.jpg) Potential versions of sample watchfaces using the UnobstructedArea API ### Determining the Unobstructed Bounds Prior to SDK 4.0, when displaying layers on screen you would calculate the size of the display using [`layer_get_bounds()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds "layer\_get\_bounds") and then scale and position your layers accordingly. Developers can now calculate the size of a layer, excluding system obstructions, using the new [`layer_get_unobstructed_bounds()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds "layer\_get\_unobstructed\_bounds"). ``` static Layer \*s\_window\_layer;static TextLayer \*s\_text\_layer;static void main\_window\_load(Window \*window) {s\_window\_layer = window\_get\_root\_layer(window);GRect unobstructed\_bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer);s\_text\_layer = text\_layer\_create(GRect(0, unobstructed\_bounds.size.h / 4, unobstructed\_bounds.size.w, 50));} ``` If you still want a fullscreen entities such as a background image, regardless of any obstructions, just combine both techniques as follows: ``` static Layer \*s\_window\_layer;static BitmapLayer \*s\_image\_layer;static TextLayer \*s\_text\_layer;static void main\_window\_load(Window \*window) {s\_window\_layer = window\_get\_root\_layer(window);GRect full\_bounds = layer\_get\_bounds(s\_window\_layer);GRect unobstructed\_bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer);s\_image\_layer = bitmap\_layer\_create(full\_bounds);s\_text\_layer = text\_layer\_create(GRect(0, unobstructed\_bounds.size.h / 4, unobstructed\_bounds.size.w, 50));} ``` The approach outlined above is perfectly fine to use when your watchface is initially launched, but you’re also responsible for handling the obstruction appearing and disappearing while your watchface is running. ### Rendering with LayerUpdateProc If your application controls its own rendering process using a [`LayerUpdateProc`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc "LayerUpdateProc") you can just dynamically adjust your rendering each time your layer updates. In this example, we use [`layer_get_unobstructed_bounds()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds "layer\_get\_unobstructed\_bounds") instead of [`layer_get_bounds()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds "layer\_get\_bounds"). The graphics are then positioned or scaled based upon the available screen real-estate, instead of the screen dimensions. > You must ensure you fill the entire window, not just the unobstructed area, when drawing the screen - failing to do so may cause unexpected graphics to be drawn behind the quick view, during animations. ``` static void hands\_update\_proc(Layer \*layer, GContext \*ctx) {GRect bounds = layer\_get\_unobstructed\_bounds(layer);GPoint center = grect\_center\_point(&bounds);const int16\_t second\_hand\_length = (bounds.size.w / 2);time\_t now = time(NULL);struct tm \*t = localtime(&now);int32\_t second\_angle = TRIG\_MAX\_ANGLE \* t-\>tm\_sec / 60;GPoint second\_hand = {.x = (int16\_t)(sin\_lookup(second\_angle) \* (int32\_t)second\_hand\_length / TRIG\_MAX\_RATIO) + center.x,.y = (int16\_t)(-cos\_lookup(second\_angle) \* (int32\_t)second\_hand\_length / TRIG\_MAX\_RATIO) + center.y,};// second handgraphics\_context\_set\_stroke\_color(ctx, GColorWhite);graphics\_draw\_line(ctx, second\_hand, center);// minute/hour handgraphics\_context\_set\_fill\_color(ctx, GColorWhite);graphics\_context\_set\_stroke\_color(ctx, GColorBlack);gpath\_rotate\_to(s\_minute\_arrow, TRIG\_MAX\_ANGLE \* t-\>tm\_min / 60);gpath\_draw\_filled(ctx, s\_minute\_arrow);gpath\_draw\_outline(ctx, s\_minute\_arrow);gpath\_rotate\_to(s\_hour\_arrow, (TRIG\_MAX\_ANGLE \* (((t-\>tm\_hour % 12) \* 6) +(t-\>tm\_min / 10))) / (12 \* 6));gpath\_draw\_filled(ctx, s\_hour\_arrow);gpath\_draw\_outline(ctx, s\_hour\_arrow);// dot in the middlegraphics\_context\_set\_fill\_color(ctx, GColorBlack);graphics\_fill\_rect(ctx, GRect(bounds.size.w / 2 - 1, bounds.size.h / 2 - 1, 3,3), 0, GCornerNone);} ``` ### Using Unobstructed Area Handlers If you are not overriding the default rendering of a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), you will need to subscribe to one or more of the [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers") to adjust the sizes and positions of layers. There are 3 events available using [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers"). These events will notify you when the unobstructed area is: _about to change_, _is currently changing_, or _has finished changing_. You can use these handlers to perform any necessary alterations to your layout. `.will_change` - an event to inform you that the unobstructed area size is about to change. This provides a [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect") which lets you know the size of the screen after the change has finished. `.change` - an event to inform you that the unobstructed area size is currently changing. This event is called several times during the animation of an obstruction appearing or disappearing. [`AnimationProgress`](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationProgress "AnimationProgress") is provided to let you know the percentage of progress towards completion. `.did_change` - an event to inform you that the unobstructed area size has finished changing. This is useful for deinitializing or destroying anything created or allocated in the will\_change handler. These handlers are optional, but at least one must be specified for a valid subscription. In the following example, we subscribe to two of the three available handlers. > **NOTE** : You must construct the [`UnobstructedAreaHandlers`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers "UnobstructedAreaHandlers") object _before_ passing it to the [`unobstructed_area_service_subscribe()`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_subscribe "unobstructed\_area\_service\_subscribe") method. ``` UnobstructedAreaHandlers handlers = {.will\_change = prv\_unobstructed\_will\_change,.did\_change = prv\_unobstructed\_did\_change};unobstructed\_area\_service\_subscribe(handlers, NULL); ``` #### Hiding Layers In this example, we’re going to hide a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") containing the current date, while the screen is obstructed. Just before the Timeline Quick View appears, we’re going to hide the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") and we’ll show it again after the Timeline Quick View disappears. ``` static Window \*s\_main\_window;static Layer \*s\_window\_layer;static TextLayer \*s\_date\_layer; ``` Subscribe to the `.did_change` and `.will_change` events: ``` static void main\_window\_load(Window \*window) {// Keep a handle on the root layers\_window\_layer = window\_get\_root\_layer(window);// Subscribe to the will\_change and did\_change eventsUnobstructedAreaHandlers handlers = {.will\_change = prv\_unobstructed\_will\_change,.did\_change = prv\_unobstructed\_did\_change};unobstructed\_area\_service\_subscribe(handlers, NULL);} ``` The `will_change` event fires before the size of the unobstructed area changes, so we need to establish whether the screen is already obstructed, or about to become obstructed. If there isn’t a current obstruction, that means the obstruction must be about to appear, so we’ll need to hide our data layer. ``` static void prv\_unobstructed\_will\_change(GRect final\_unobstructed\_screen\_area,void \*context) {// Get the full size of the screenGRect full\_bounds = layer\_get\_bounds(s\_window\_layer);if (!grect\_equal(&full\_bounds, &final\_unobstructed\_screen\_area)) {// Screen is about to become obstructed, hide the datelayer\_set\_hidden(text\_layer\_get\_layer(s\_date\_layer), true);}} ``` The `did_change` event fires after the unobstructed size changes, so we can perform the same check to see whether the screen is already obstructed, or about to become obstructed. If the screen isn’t obstructed when this event fires, then the obstruction must have just cleared and we’ll need to display our date layer again. ``` static void prv\_unobstructed\_did\_change(void \*context) {// Get the full size of the screenGRect full\_bounds = layer\_get\_bounds(s\_window\_layer);// Get the total available screen real-estateGRect bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer);if (grect\_equal(&full\_bounds, &bounds)) {// Screen is no longer obstructed, show the datelayer\_set\_hidden(text\_layer\_get\_layer(s\_date\_layer), false);}} ``` #### Animating Layer Positions The `.change` event will fire several times while the unobstructed area is changing size. This allows us to use this event to make our layers appear to slide-in or slide-out of their initial positions. In this example, we’re going to use percentages to position two text layers vertically. One layer at the top of the screen and one layer at the bottom. When the screen is obstructed, these two layers will shift to be closer together. Because we’re using percentages, it doesn’t matter if the unobstructed area is increasing or decreasing, our text layers will always be relatively positioned in the available space. ``` static const uint8\_t s\_offset\_top\_percent = 33;static const uint8\_t s\_offset\_bottom\_percent = 10; ``` A simple helper function to simulate percentage based coordinates: ``` uint8\_t relative\_pixel(int16\_t percent, int16\_t max) {return (max \* percent) / 100;} ``` Subscribe to the change event: ``` static void main\_window\_load(Window \*window) {UnobstructedAreaHandlers handler = {.change = prv\_unobstructed\_change};unobstructed\_area\_service\_subscribe(handler, NULL);} ``` Move the text layer each time the unobstructed area size changes: ``` static void prv\_unobstructed\_change(AnimationProgress progress, void \*context) {// Get the total available screen real-estateGRect bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer);// Get the current position of our top text layerGRect frame = layer\_get\_frame(text\_layer\_get\_layer(s\_top\_text\_layer));// Shift the Y coordinateframe.origin.y = relative\_pixel(s\_offset\_top\_percent, bounds.size.h);// Apply the new locationlayer\_set\_frame(text\_layer\_get\_layer(s\_top\_text\_layer), frame);// Get the current position of our bottom text layerGRect frame2 = layer\_get\_frame(text\_layer\_get\_layer(s\_top\_text\_layer));// Shift the Y coordinateframe2.origin.y = relative\_pixel(s\_offset\_bottom\_percent, bounds.size.h);// Apply the new positionlayer\_set\_frame(text\_layer\_get\_layer(s\_bottom\_text\_layer), frame2);} ``` ### Toggling Timeline Quick View The `pebble` tool which shipped as part of SDK 4.0, allows developers to enable and disable Timeline Quick View, which is incredibly useful for debugging purposes. ![Unobstructed animation](https://developer.repebble.com/assets/images/guides/user-interfaces/unobstructed-area/unobstructed-animation.gif) To enable Timeline Quick View, you can use: ``` $ pebble emu-set-timeline-quick-view on ``` To disable Timeline Quick View, you can use: ``` $ pebble emu-set-timeline-quick-view off ``` ### Additional Considerations If you're scaling or moving layers based on the unobstructed area, you must ensure you fill the entire window, not just the unobstructed area. Failing to do so may cause unexpected graphics to be drawn behind the quick view, during animations. At present, Timeline Quick View is not currently planned for the Chalk platform. For design reference, the height of the Timeline Quick View overlay will be _51px_ in total, which includes a 2px border, but this may vary on newer platforms and and the height should always be calculated at runtime. ``` // Calculate the actual height of the Timeline Quick Views\_window\_layer = window\_get\_root\_layer(window);GRect fullscreen = layer\_get\_bounds(s\_window\_layer);GRect unobstructed\_bounds = layer\_get\_unobstructed\_bounds(s\_window\_layer);int16\_t obstruction\_height = fullscreen.size.h - unobstructed\_bounds.size.h; ``` --- # User Interfaces Source: https://developer.repebble.com/guides/user-interfaces/ The User Intefaces section of the developer guide contains information on using other the Pebble SDK elements that contribute to interface with the user in some way, shape, or form. For example, [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") objects form the foundation of all app user interfaces, while a configuration page asks a user for their input in terms of preferences. Graphics-specific UI elements and resources are discussed in the [_Graphics and Animations_](https://developer.repebble.com/guides/graphics-and-animations.md) and [_App Resources_](https://developer.repebble.com/guides/app-resources.md) sections. ## Contents - [**App Configuration**](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) - How to allow users to customize an app with a configuration page. - [**App Exit Reason**](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md) - Details on how to use the AppExitReason API - [**AppGlance C API**](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) - How to programatically update an app's app glance. - [**AppGlance in PebbleKit JS**](https://developer.repebble.com/guides/user-interfaces/appglance-pebblekit-js.md) - How to update an app's glance using PebbleKit JS. - [**Content Size**](https://developer.repebble.com/guides/user-interfaces/content-size.md) - Details on how to use the ContentSize API to adapt your watchface layout based on user text size preferences. - [**Layers**](https://developer.repebble.com/guides/user-interfaces/layers.md) - How to use standard Layer components to build an app's UI. - [**Round App UI**](https://developer.repebble.com/guides/user-interfaces/round-app-ui.md) - Details on how to use the Pebble SDK to create layouts specifically for round displays. - [**Unobstructed Area**](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md) - Details on how to use the UnobstructedArea API to adapt your watchface layout when the screen is partially obstructed by a system overlay. --- # Getting Started with Alloy Source: https://developer.repebble.com/guides/alloy/getting-started/ This guide walks you through creating your first Alloy app for Pebble. > **Platform Support** : Alloy currently supports Emery (Pebble Time 2) and Gabbro (Pebble Round 2). See the platform support table in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md#platform-support). ## Creating a New Project Use the `pebble` command-line tool to create a new Alloy project: ``` $ pebble new-project --alloy my-first-app ``` This creates a new directory with the following structure: ``` my-first-app/ src/ embeddedjs/ main.js # Watch app code (runs on Pebble) pkjs/ index.js # Phone code (runs on connected phone) resources/ # App resources (images, fonts, etc.) package.json # App manifest and configuration ``` ## Project Structure Alloy apps have **two JavaScript environments** : | Location | File | Runs On | Purpose | | --- | --- | --- | --- | | **embeddedjs** | `src/embeddedjs/main.js` | Watch | Your app UI and logic | | **PKJS** | `src/pkjs/index.js` | Phone | Network proxy, location, config | ### Watch Code (embeddedjs) The `src/embeddedjs/main.js` file is your watch app's entry point. This code runs directly on the Pebble watch: **⌚ Watch** (src/embeddedjs/main.js): ``` console.log("Hello, Pebble!"); ``` ### Phone Code (PKJS) The `src/pkjs/index.js` file runs on the connected phone. It's used for network requests, GPS location, and configuration: **📱 PKJS** (src/pkjs/index.js): ``` Pebble.addEventListener("ready", function(e) {console.log("PebbleKit JS ready!");}); ``` ### package.json The `package.json` file contains your app's metadata and configuration: ``` {"name": "My First App","author": "Your Name","version": "1.0.0","keywords": ["pebble-app"],"private": true,"dependencies": {},"pebble": {"displayName": "My First App","uuid": "12345678-1234-1234-1234-123456789abc","projectType": "moddable","sdkVersion": "3","enableMultiJS": true,"targetPlatforms": ["emery"],"watchapp": {"watchface": false},"messageKeys": [],"resources": {"media": []}}} ``` Key fields in the `pebble` section: | Field | Description | | --- | --- | | `displayName` | The name shown on the watch | | `uuid` | Unique identifier for your app | | `targetPlatforms` | Which Pebble platforms to build for | | `watchapp.watchface` | Set to `true` for watchfaces, `false` for apps | | `messageKeys` | Keys for watch-phone communication | ## Using ES Modules Alloy uses standard ECMAScript modules. You can split your code across multiple files and import them: **math.js** ``` export function add(a, b) {return a + b;}export function multiply(a, b) {return a \* b;} ``` **main.js** ``` import { add, multiply } from "./math";console.log("Sum: " + add(2, 3));// 5console.log("Product: " + multiply(4, 5));// 20 ``` ### Declaring Modules in manifest.json Each additional module must be listed in `src/embeddedjs/manifest.json` so the Moddable build system knows to compile it. The default manifest only includes `main.js`: ``` "modules": {"\*": "./main"} ``` To add more modules, change the value to an array of paths. The `.js` extension is optional: ``` "modules": {"\*": ["./main","./math"]} ``` If you import a module that isn't listed here, you'll get a module-not-found error at runtime. ## Important JavaScript Differences Alloy runs on the XS JavaScript engine, which has some differences from browser or Node.js JavaScript: ### Strict Mode All code runs in strict mode by default. ### Hardened JavaScript Built-in objects (primordials) are frozen and cannot be modified: ``` // This will throw an error:Array.prototype.myMethod = function() {}; ``` ### No eval() Evaluation of JavaScript source code by eval, Function and friends is not supported to keep minimize the code footprint of the JavaScript engine. ### Explicit Resource Management Since SDK 4.33, Alloy supports Explicit Resource Management: `using` declarations, `Symbol.dispose`, `DisposableStack` and `SuppressedError`. Alloy classes with a `close()` method (buttons, sensors, messages, files, network clients and more) are disposable, so they can be released automatically at the end of a scope: ``` function readScore(path) {using file = device.files.openFile({ path });const data = file.read(file.status().size, 0);return JSON.parse(String.fromArrayBuffer(data)).score;// file.close() runs automatically when the scope exits,// even if read() or JSON.parse() throws} ``` ## Debugging Output Use `console.log()` for debug output: ``` console.log("Debug message");console.log("Value: " + someVariable); ``` Output appears in the Pebble emulator console or when viewing logs from a physical watch. > **Note** : `trace()` is also available as a lower-level alternative, but requires a manual newline (`\n`) at the end of each message. `console.log()` is recommended for most use cases. ## Building and Running To build your app and run it in the emulator: ``` $ pebble build$ pebble install --emulator emery ``` To install on a physical watch: ``` $ pebble install --phone YOUR_PHONE_IP ``` ## Next Steps Now that you have a basic app running, explore the following guides: - [Piu UI Framework](https://developer.repebble.com/guides/alloy/piu-guide.md): Build declarative user interfaces - [Poco Graphics](https://developer.repebble.com/guides/alloy/poco-guide.md): Low-level drawing and graphics - [Sensors and Input](https://developer.repebble.com/guides/alloy/sensors-and-input.md): Handle buttons and read sensors - [Storage](https://developer.repebble.com/guides/alloy/storage.md): Persist data between app launches - [Networking](https://developer.repebble.com/guides/alloy/networking.md): Make HTTP requests and use WebSockets - [App Messages](https://developer.repebble.com/guides/alloy/app-messages.md): Send and receive messages between watch and phone --- # Port (Custom Drawing) Source: https://developer.repebble.com/guides/alloy/port-drawing/ The `Port` content type lets you do custom drawing within a Piu application. This combines the best of both worlds: Piu's declarative layout and component system with Poco-style direct drawing. > **Note** : All code in this guide runs on the watch in `src/embeddedjs/main.js`. ## What is Port? Port is a Piu content type that provides a canvas for custom drawing. Unlike other Piu content types that are styled with skins, Port content uses an `onDraw` callback where you can draw shapes, text, and graphics. ## Basic Port Usage ``` class MyPortBehavior extends Behavior {onDraw(port, x, y, width, height) {// Draw a blue rectangleport.fillColor("#0066cc", 0, 0, width, height);// Draw some textconst style = new Style({ font: "18px Gothic", color: "white" });port.drawString("Hello!", style, "white", 10, 10);}}const myPort = new Port(null, {top: 0, bottom: 0, left: 0, right: 0,Behavior: MyPortBehavior});const application = new Application(null, {skin: new Skin({ fill: "white" })});application.add(myPort); ``` ## Port Drawing Methods ### Filling Shapes ``` onDraw(port, x, y, width, height) {// Fill a rectangle with a colorport.fillColor("blue", 10, 10, 50, 30);// Fill with hex colorport.fillColor("#ff6600", 70, 10, 50, 30);} ``` ### Drawing Text ``` onDraw(port, x, y, width, height) {const style = new Style({font: "bold 18px Gothic",color: "black"});// Draw text at positionport.drawString("Hello World", style, "black", 10, 50);// Measure text width for alignmentconst text = "Centered";const measured = style.measure(text);const textX = (width - measured.width) / 2;port.drawString(text, style, "black", textX, 80);} ``` ### Drawing Images ``` onDraw(port, x, y, width, height) {const texture = new Texture(2);// Resource IDport.drawTexture(texture, "white", 10, 10, 0, 0, texture.width, texture.height);} ``` ## Updating Port Content Call `invalidate()` to trigger a redraw: ``` class AnimatedPortBehavior extends Behavior {onDisplaying(port) {this.value = 0;port.interval = 100;// Update every 100msport.start();}onTimeChanged(port) {this.value = (this.value + 5) % 100;port.invalidate();// Request redraw}onDraw(port, x, y, width, height) {// Clear backgroundport.fillColor("white", 0, 0, width, height);// Draw bar based on current valueconst barWidth = (width \* this.value) / 100;port.fillColor("green", 0, height / 2 - 10, barWidth, 20);}} ``` ## Complete Example: Live Bar Chart This example creates an animated bar chart with random data: ``` const GRAY = "gray";const BLUE = "#1932ab";const textStyle = new Style({font: "bold 18px Gothic"});class GraphBehavior extends Behavior {onDisplaying(port) {// Initialize data arrayconst barCount = Math.floor(screen.width / 16);this.values = new Array(barCount);this.values.fill(0);// Start animationport.interval = 100;port.start();}onTimeChanged(port) {// Shift data and add new random valuethis.values.shift();this.values.push(Math.floor(Math.random() \* 100));port.invalidate();}onDraw(port, x, y, width, height) {// Draw Y-axis labels and grid linesfor (let i = 100, yOffset = 0; i \>= 0; i -= 20) {const label = String(i);const labelWidth = textStyle.measure(label).width;port.drawString(label, textStyle, "black", 30 - labelWidth, yOffset);port.fillColor(GRAY, 35, yOffset + 10, width - 35, 1);yOffset += height / 5;}// Draw barslet xOffset = 35;for (let i = 0; i \< this.values.length; i++) {const value = this.values[i];const barHeight = Math.floor((value \* (height - 10)) / 100);port.fillColor(BLUE, xOffset, height - barHeight, 12, barHeight);xOffset += 14;}}}const graph = new Port(null, {top: 0, bottom: 0, left: 0, right: 0,Behavior: GraphBehavior});const application = new Application(null, {skin: new Skin({ fill: "white" })});application.add(graph); ``` ## Combining Port with Piu Layout Port works within Piu's layout system, so you can combine custom drawing with standard Piu components: ``` import {} from "piu/MC";const headerSkin = new Skin({ fill: "darkblue" });const headerStyle = new Style({font: "bold 18px Gothic",color: "white"});class ChartBehavior extends Behavior {onDisplaying(port) {this.data = [30, 50, 80, 45, 90, 60, 75];port.invalidate();}onDraw(port, x, y, width, height) {port.fillColor("white", 0, 0, width, height);const barWidth = width / this.data.length - 4;let xPos = 2;for (const value of this.data) {const barHeight = (value / 100) \* height;port.fillColor("#4CAF50", xPos, height - barHeight, barWidth, barHeight);xPos += barWidth + 4;}}}const DashboardApp = Application.template($ =\> ({skin: new Skin({ fill: "#f0f0f0" }),contents: [Column($, {top: 0, bottom: 0, left: 0, right: 0,contents: [// Header using standard Piu LabelLabel($, {top: 0, height: 40, left: 0, right: 0,skin: headerSkin,style: headerStyle,string: "Weekly Stats"}),// Custom chart using PortPort($, {top: 10, bottom: 10, left: 10, right: 10,Behavior: ChartBehavior})]})]}));export default new DashboardApp({}, {}); ``` ## Port vs Poco | Feature | Port | Poco | | --- | --- | --- | | Integration | Works within Piu apps | Standalone rendering | | Layout | Uses Piu's layout system | Manual positioning | | Updates | Call `invalidate()` | Call `begin()`/`end()` | | Use case | Custom drawing in Piu apps | Full-screen graphics | Use Port when you want custom drawing as part of a larger Piu UI. Use Poco when you need full control over the entire screen. ## Performance Tips 1. **Minimize invalidate() calls**: Only call when data actually changes. 2. **Draw only what's needed** : The `onDraw` callback receives bounds - use them to clip your drawing. 3. **Cache expensive calculations** : Calculate positions and sizes once, not on every draw. 4. **Use appropriate intervals** : 100ms intervals are usually sufficient for charts. Use shorter intervals only when smooth animation is needed. ## Common Patterns ### Progress Bar ``` class ProgressBehavior extends Behavior {onCreate(port, data) {this.progress = data.progress || 0;}setProgress(port, value) {this.progress = Math.max(0, Math.min(100, value));port.invalidate();}onDraw(port, x, y, width, height) {// Backgroundport.fillColor("#ddd", 0, 0, width, height);// Progress fillconst fillWidth = (width \* this.progress) / 100;port.fillColor("#4CAF50", 0, 0, fillWidth, height);}} ``` ### Gauge/Meter ``` class GaugeBehavior extends Behavior {onCreate(port, data) {this.value = data.value || 0;this.max = data.max || 100;}onDraw(port, x, y, width, height) {const centerX = width / 2;const centerY = height / 2;const radius = Math.min(width, height) / 2 - 10;// Background circle (using rectangles as approximation)port.fillColor("#eee", centerX - radius, centerY - radius,radius \* 2, radius \* 2);// Value indicatorconst percentage = this.value / this.max;const indicatorHeight = radius \* 2 \* percentage;port.fillColor("#2196F3",centerX - radius, centerY + radius - indicatorHeight,radius \* 2, indicatorHeight);}} ``` ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes a Port example: - [`hellopiu-port`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-port) - animated bar graph using a Piu Port with custom drawing --- # Advanced Networking Source: https://developer.repebble.com/guides/alloy/advanced-networking/ In addition to `fetch()` and `WebSocket`, Alloy provides low-level ECMA-419 networking APIs that offer more control over HTTP and WebSocket connections. In fact, `fetch()` and `WebSocket()` are implemented using these ECMA-419 APIs. > **Note** : Like `fetch()` and `WebSocket`, these APIs require the `@moddable/pebbleproxy` package. See below for setup. ## Proxy Setup All networking APIs require proxy code running on the phone. Install the `@moddable/pebbleproxy` package in your project directory: ``` $ pebble package install @moddable/pebbleproxy ``` Then add the proxy to your `src/pkjs/index.js`: **📱 PKJS** (src/pkjs/index.js): ``` const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', moddableProxy.readyReceived);Pebble.addEventListener('appmessage', moddableProxy.appMessageReceived); ``` If your app also needs to handle its own `ready` or `appmessage` events, you can call the proxy functions from your own event handlers: ``` const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', function(e) {moddableProxy.readyReceived(e);// Handle your own ready event here})Pebble.addEventListener('appmessage', function(e) {if (moddableProxy.appMessageReceived(e))return;// Handle your own app messages here}); ``` The `@moddable/pebbleproxy` package handles proxying for `fetch()`, `WebSocket`, `HTTPClient`, and `WebSocketClient`. ## HTTPClient The `HTTPClient` class provides streaming HTTP requests with fine-grained control over headers and response handling. ### Using HTTPClient **⌚ Watch** (src/embeddedjs/main.js): ``` import HTTPClient from "embedded:network/http/client";const http = new HTTPClient({host: "example.com"});http.request({path: "/",method: "GET",headers: new Map([["User-Agent", "Pebble App"]]),headersMask: ["content-type", "date"],onHeaders(status, headers, statusText) {console.log("Status: " + status + " " + statusText);headers.forEach((value, key) =\> {console.log(key + ": " + value);});},onReadable(count) {// Read response body in chunksconst buffer = this.read(count);console.log(String.fromArrayBuffer(buffer));},onComplete() {console.log("Request complete");},onError(error) {console.log("Error: " + error);}}); ``` ### HTTPClient Options | Option | Description | | --- | --- | | `host` | Target hostname | | `port` | Port number (optional) | ### Request Options | Option | Description | | --- | --- | | `path` | URL path (e.g., `/api/data`) | | `method` | HTTP method (default: `GET`) | | `headers` | Map of request headers | | `headersMask` | Array of header names to include in response | | `body` | Request body (for POST/PUT) | ### Request Callbacks | Callback | Description | | --- | --- | | `onHeaders(status, headers, statusText)` | Response headers received | | `onReadable(count)` | Response body data available (`count` is the number of bytes available to read) | | `onComplete()` | Request completed successfully | | `onError(error)` | Request failed | ## WebSocketClient The `WebSocketClient` class provides a low-level WebSocket connection with streaming support. ### Using WebSocketClient **⌚ Watch** (src/embeddedjs/main.js): ``` import WebSocketClient from "embedded:network/websocket/client";const ws = new WebSocketClient({...device.network.ws,// use device.network.wss for secure (wss://) connectionshost: "websockets.chilkat.io",path: "/wsChilkatEcho.ashx",onReadable(count, options) {console.log("Received " + count + " bytes, binary: " + options.binary);const data = this.read();console.log(String.fromArrayBuffer(data));},onWritable(count) {console.log("Ready to write " + count + " bytes");this.write(ArrayBuffer.fromString("Hello!"), { binary: false });},onClose() {console.log("Connection closed");},onError() {console.log("Connection error");}}); ``` ### WebSocketClient Callbacks | Callback | Description | | --- | --- | | `onReadable(count, options)` | Data received from server | | `onWritable(count)` | Ready to send data | | `onClose()` | Connection closed | | `onError()` | Connection error occurred | ## HTTPClient vs fetch() | Feature | HTTPClient | fetch() | | --- | --- | --- | | API style | ECMA-419 callbacks | Promise-based | | Streaming | Yes | No | | Header control | Fine-grained with mask | Basic | | Best for | Large responses, streaming | Simple API calls | ## WebSocketClient vs WebSocket | Feature | WebSocketClient | WebSocket | | --- | --- | --- | | API style | ECMA-419 callbacks | Web standard events | | Streaming | Yes | No | | Best for | Low-level control | Simple messaging | ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes advanced networking examples: - [`hellohttpclient`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellohttpclient) - HTTP requests using the ECMA-419 `HTTPClient` - [`hellowebsocketclient`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowebsocketclient) - WebSocket connections using the ECMA-419 `WebSocketClient` --- # Native Functions (FFI) Source: https://developer.repebble.com/guides/alloy/ffi/ The Foreign Function Interface (FFI) lets your Alloy app call native C functions directly from JavaScript. This is useful for performance-critical code, for reusing existing C libraries, or for reaching low-level functionality that isn't exposed through a JavaScript API. > **Platform Support** : Like the rest of Alloy, FFI is available on Emery (Pebble Time 2) and Gabbro (Pebble Round 2). An FFI binding has three parts: 1. The **native C source** that implements the functions. 2. An **`ffi` block** in `manifest.json` that lists the C sources and declares each function's signature. 3. The **`Natives` global** in your JavaScript, through which the functions are called. ## 1. Write the native C functions Add one or more C files alongside your `mdbl.c` (for example `src/c/add.c`). The C function names must match the names you declare in the manifest: ``` // add.c#include \int32\_t add(int32\_t a, int32\_t b) {return a + b;}int32\_t addSquares(int32\_t a, int32\_t b) {return (a \* a) + (b \* b);} ``` Functions can take and return buffers and strings as well as numbers: ``` // hello.c#include \#include \// Fill the caller-provided buffer with bytes derived from `greeting`.void hello(char \*greeting, uint8\_t \*bytes, uint32\_t length) {size\_t len = strlen(greeting);for (uint32\_t i = 0; i \< length; i++) {bytes[i] = (uint8\_t)greeting[i % len];}} ``` ## 2. Declare the functions in the manifest In `src/embeddedjs/manifest.json`, add an `ffi` block listing your C `sources` and the `functions` with their argument and return types: ``` {"include": ["$(MODDABLE)/examples/manifest\_mod.json","$(MODDABLE)/examples/manifest\_typings.json"],"modules": {"\*": "./main"},"ffi": {"sources": ["./add.c","./hello.c"],"functions": {"add": {"arguments": ["int32\_t", "int32\_t"],"returns": "int32\_t"},"addSquares": {"arguments": ["int32\_t", "int32\_t"],"returns": "int32\_t"},"hello": {"arguments": ["char \*", "uint8\_t\*", "uint32\_t"],"returns": "void"}}}} ``` ### Supported types | C type | JavaScript value | | --- | --- | | `int32_t`, `uint8_t`, `uint32_t` | `Number` | | `char*` (argument) | `String` | | `char*`, `const char*` (return) | `String` | | `uint8_t*`, `void*` | `ArrayBuffer` (pass `.buffer` from a typed array or `DataView`) | | `void` | `undefined` | Numeric arguments are passed by value. Buffers are passed by reference, so a C function can read from and write into an `ArrayBuffer` you pass in. ## 3. Make the machine FFI-aware The C entry point (`src/c/mdbl.c`) must pass the generated `fxBuildFFI` symbol when it creates the Moddable machine: ``` #include \int main(void) {Window \*w = window\_create();window\_stack\_push(w, true);moddable\_createMachine(&(ModdableCreationRecord){.recordSize = sizeof(ModdableCreationRecord),#ifdef PBL\_DEBUG.flags = kModdableCreationFlagDebug,#endif.fxBuildFFI = fxBuildFFI,});window\_destroy(w);return 0;} ``` ## 4. Call the functions from JavaScript All declared functions are available on the global `Natives` object: ``` console.log("Hello, FFI.");trace(`Natives.add(2, 3) = ${ Natives.add(2, 3) }\n`);trace(`Natives.addSquares(3, 4) = ${ Natives.addSquares(3, 4) }\n`);const bytes = new Uint8Array(5);Natives.hello("hello", bytes.buffer, bytes.length);trace(`bytes = ${ bytes }\n`); ``` Wrap calls in a `try`/`catch` to handle errors thrown across the boundary: ``` try {const result = Natives.add(2, 3);} catch (error) {console.log("FFI Error: " + error);} ``` ## Further reading The FFI mechanism comes from the Moddable SDK. See the [XS FFI documentation](https://www.moddable.com/documentation/xs/XS%20FFI) for the full set of supported types and behaviors, and the [`helloffi` example](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloffi) for a complete project. --- # Dictation Source: https://developer.repebble.com/guides/alloy/dictation/ The `Dictation` class lets your Alloy app capture speech from the user and receive it as text. It drives the system dictation UI and delivers the transcription to your code through callbacks. > **Platform Support** : Dictation is available on Emery (Pebble Time 2) and Gabbro (Pebble Round 2), and requires a connected phone with a working internet connection. ## Basic Usage Import the class and create an instance with `onReadable` and `onError` handlers, then call `start()` to begin listening: ``` import Dictation from "pebble/dictation";let dictation = new Dictation({onReadable() {console.log(`Transcription: ${this.read()}`);},onError(e) {console.log(`Dictation error: ${e}`);}});dictation.start(); ``` ## Methods | Method | Description | | --- | --- | | `start()` | Open the dictation UI and begin listening. | | `stop()` | Stop an in-progress dictation session. | | `read()` | Return the transcribed text. Call from `onReadable`. | | `configure(options)` | Change dictation behavior (see [Configuration](#configuration)). | | `close()` | Release the dictation instance when you're done with it. | ## Events | Event | Description | | --- | --- | | `onReadable()` | Called when a transcription is ready. Call `this.read()` to get the text. | | `onError(status)` | Called when dictation fails (for example, no connection or no speech detected). `status` is a numeric error code. | ## Configuration Pass a `byteLength` to the constructor to size the transcription buffer, and use `configure()` to control the dictation UI: ``` import Dictation from "pebble/dictation";let dictation = new Dictation({byteLength: 512,// maximum transcription size, in bytesonReadable() {console.log(`Transcription: ${this.read()}`);}});dictation.configure({confirm: false,// skip the confirmation screen after dictationerrorDialogs: true// let the system show its own error dialogs});dictation.start(); ``` | Option | Description | | --- | --- | | `confirm` | Whether to show the confirmation screen after dictation (default `true`). | | `errorDialogs` | Whether the system shows its own error UI on failure. | ## Continuous Dictation To keep listening after each transcription, start a new session from within `onReadable`. Use `setImmediate` so the current session finishes cleanly first: ``` import Dictation from "pebble/dictation";let dictation = new Dictation({onReadable() {console.log(`Transcription: ${this.read()}`);setImmediate(() =\> {console.log("Listening...");this.start();});},onError(e) {console.log(`Dictation error: ${e}`);}});console.log("Listening...");dictation.start(); ``` See the [`hellodictation` example](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellodictation) for a complete project. --- # Wakeups Source: https://developer.repebble.com/guides/alloy/wakeups/ Wakeups let your Alloy app schedule itself to launch at a future time, even if it isn't running. When the wakeup fires, the system launches your app and you can read the wakeup details to know why it was started. > **Platform Support** : Wakeups are available on Emery (Pebble Time 2) and Gabbro (Pebble Round 2). ## Scheduling a Wakeup Import the `WakeUp` class and call `schedule()` with a timestamp (in milliseconds, as returned by `Date.now()`), a numeric `cookie` of your choice, and whether the user should be notified if the wakeup is missed: ``` import WakeUp from "pebble/wakeup";// Fire 3 seconds from nowconst id = WakeUp.schedule(Date.now() + 3000, 12345678, false);console.log(`Scheduled WakeUp id ${id}`); ``` `schedule()` returns an `id` you can later use to query or cancel the wakeup. Persist the `id` (for example with `localStorage`) if you need it across launches. ## Methods | Method | Description | | --- | --- | | `WakeUp.schedule(time, cookie, notifyIfMissed)` | Schedule a wakeup at `time` (ms). `cookie` is a number passed back to your app. `notifyIfMissed` shows a missed-event notification if the wakeup couldn't fire. Returns an `id`. | | `WakeUp.cancel(id)` | Cancel a previously scheduled wakeup. | | `WakeUp.query(id)` | Return a `{ time, scheduled }` object describing the wakeup - `time` is the scheduled time in ms and `scheduled` is `true` if it is still pending - or a falsy value if it no longer exists. | ## Detecting a Wakeup Launch When your app is launched by a wakeup, `watch.wake` is set and contains the wakeup's `id` and `cookie`: ``` console.log(`Launch reason ${watch.launch.reason}, arguments ${watch.launch.arguments}`);if (watch.wake) {console.log(`Launched by wakeup id ${watch.wake.id}, cookie ${watch.wake.cookie}`);} ``` ## Receiving a Wakeup While Running If a wakeup fires while your app is already running, it is delivered as a `"wakeup"` event on `watch`: ``` watch.addEventListener("wakeup", wake =\> {console.log(`wakeup id ${wake.id} occurred while running`);WakeUp.cancel(wake.id);}); ``` ## Managing Stored Wakeups Because a wakeup `id` outlives a single launch, it's good practice to validate a stored `id` on startup and discard it if the wakeup is gone or stale: ``` const id = localStorage.getItem("wakeid");if (id) {const wakeup = WakeUp.query(id);if (!wakeup) {// The wakeup no longer existslocalStorage.removeItem("wakeid");} else if (wakeup.time \< Date.now()) {// The wakeup is in the past; cancel itWakeUp.cancel(id);localStorage.removeItem("wakeid");}} ``` See the [`hellowakeup` example](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowakeup) for a complete project. --- # Vibration Source: https://developer.repebble.com/guides/alloy/vibration/ The `Vibes` class drives the watch's vibration motor, letting your Alloy app provide haptic feedback. It offers a set of standard pulses as well as custom vibration patterns. > **Platform Support** : Vibration is available on Emery (Pebble Time 2) and Gabbro (Pebble Round 2). ## Standard Pulses ``` import Vibes from "pebble/vibes";Vibes.shortPulse();Vibes.longPulse();Vibes.doublePulse(); ``` | Method | Description | | --- | --- | | `Vibes.shortPulse()` | A single short vibration. | | `Vibes.longPulse()` | A single long vibration. | | `Vibes.doublePulse()` | Two short vibrations. | ## Custom Patterns Pass an array of millisecond durations to `pattern()`. Durations alternate between _on_ and _off_, starting with _on_: ``` import Vibes from "pebble/vibes";// on 100ms, off 100ms, on 150ms, off 50ms, on 50ms, off 150ms, on 1000msVibes.pattern([100, 100, 150, 50, 50, 150, 1000]); ``` ## Cancelling Stop any ongoing vibration immediately: ``` Vibes.cancel(); ``` > **Note** : Vibration draws power and can be disruptive. Use it sparingly, and respect the user's Quiet Time settings for non-essential feedback. See the [`hellovibes` example](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellovibes) for a complete project. --- # Device Info and App Events Source: https://developer.repebble.com/guides/alloy/device-info/ Alloy exposes information about the watch and screen through the `watch`, `screen`, and `device` globals, delivers app lifecycle events through `watch`, and lets you control the backlight with `watch.light()`. > **Platform Support** : These APIs are available on Emery (Pebble Time 2) and Gabbro (Pebble Round 2). ## Watch Information The `watch` global describes the device and the user's preferences: ``` console.log(`watch.model ${watch.model}`);console.log(`firmware ${watch.firmwareVersion.major}.${watch.firmwareVersion.minor}.${watch.firmwareVersion.patch}`);console.log(`user prefers ${watch.hour12 ? "12" : "24"} hour display`); ``` | Property | Description | | --- | --- | | `watch.model` | The watch model identifier. | | `watch.firmwareVersion` | Object with `major`, `minor`, and `patch` numbers. | | `watch.hour12` | `true` if the user prefers a 12-hour clock, `false` for 24-hour. | | `watch.launch` | How the app was launched: `reason` and `arguments`. | | `watch.wake` | Present when launched by a wakeup; see [_Wakeups_](https://developer.repebble.com/guides/alloy/wakeups.md). | ## Screen Properties The `screen` global describes the display: ``` console.log(`screen ${screen.width} x ${screen.height}`);console.log(`round ${screen.round}, color ${screen.color}`); ``` | Property | Description | | --- | --- | | `screen.width` | Display width in pixels. | | `screen.height` | Display height in pixels. | | `screen.round` | `true` on round displays (Gabbro). | | `screen.color` | `true` on color displays. | ## Device Sensors The `device.sensor` object reports which sensors are available, so you can feature-detect before using one: ``` const hasTouch = device.sensor.Touch ? true : false;console.log(`touch available ${hasTouch}`); ``` ## App Lifecycle and Display Events Subscribe to app events on `watch` with `addEventListener`: ``` watch.addEventListener("willFocus", inFocus =\> {console.log(`willFocus with inFocus ${inFocus}`);});watch.addEventListener("didFocus", inFocus =\> {console.log(`didFocus with inFocus ${inFocus}`);});watch.addEventListener("resize", progress =\> {console.log(`screen resized to ${screen.width} x ${screen.height} at progress ${progress}`);}); ``` | Event | Description | | --- | --- | | `"willFocus"` | The app is about to gain or lose focus. The handler receives `inFocus`. | | `"didFocus"` | The app has gained or lost focus. The handler receives `inFocus`. | | `"resize"` | The drawable area is changing size (for example during an animation). The handler receives the animation `progress`. | The `watch` global also delivers `"secondchange"`/`"minutechange"` time events (see [_Watchfaces_](https://developer.repebble.com/guides/alloy/watchfaces.md)), a `"wakeup"` event (see [_Wakeups_](https://developer.repebble.com/guides/alloy/wakeups.md)), and `"health"` events when new health data arrives (see [_Health_](https://developer.repebble.com/guides/alloy/health.md)). ## Controlling the Backlight Use `watch.light()` to control the backlight: ``` watch.light(true);// force the backlight onwatch.light(false);// turn the backlight offwatch.light();// turn on temporarily, as if the user interacted ``` Calling `watch.light()` with no argument turns the backlight on for the standard auto-off interval, just like a button press or shake would. Passing a boolean forces the backlight on or off. > **Note** : Holding the backlight on draws significantly more power than letting it auto-off. Only force it on for short, deliberate interactions. See the [`helloinfo`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloinfo), [`helloappevents`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloappevents), and [`hellolight`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellolight) examples for complete projects. --- # Health Source: https://developer.repebble.com/guides/alloy/health/ The `Health` class gives Alloy apps access to Pebble Health: current and historical step counts, sleep, calories, distance, detected activities, and heart rate. It was added in SDK 4.33 and requires firmware 4.32 or later. > **Note** : Health data is only available while the user has Pebble Health enabled in the mobile app, and heart rate metrics additionally require a watch with a heart rate monitor (Pebble Time 2). Well-behaved apps check [availability](#checking-availability) before reading. All times passed to and returned by the Health API are JavaScript milliseconds — pass `Date` objects or millisecond timestamps. ## Reading Current Values `Health.metric.get()` returns the current (today's) value of a metric: ``` import Health from "pebble/health";console.log(`Steps today: ${Health.metric.get("step count")}`);console.log(`Heart rate: ${Health.metric.get("heart rate")} BPM`); ``` | Metric | Description | | --- | --- | | `"step count"` | Steps taken today | | `"active seconds"` | Seconds spent active today | | `"walked distance"` | Distance walked today, in meters | | `"sleep seconds"` | Seconds slept | | `"sleep restful seconds"` | Seconds of restful (deep) sleep | | `"resting calories"` | Resting kilocalories burned today | | `"active calories"` | Active kilocalories burned today | | `"heart rate"` | Filtered heart rate, in BPM | | `"heart rate raw"` | Raw, unfiltered heart rate, in BPM | ## Checking Availability Not every metric is available on every watch, for every time range, or with the user's current permissions. `Health.metric.accessible()` returns a bitmask of the flags in `Health.access`: ``` import Health from "pebble/health";const now = Date.now();const access = Health.metric.accessible({metric: "heart rate",start: now,end: now});if (access & Health.access.available) {console.log(`Heart rate: ${Health.metric.get("heart rate")} BPM`);} ``` | Flag | Meaning | | --- | --- | | `Health.access.available` | Values can be read for the requested range | | `Health.access.permission` | The user has granted this app access to health data | | `Health.access.supported` | The watch supports this metric | | `Health.access.data` | Samples exist in the requested range | ## Querying History `Health.metric.query()` reads sums and averages over a time range. With no `start` and `end`, it returns today's total: ``` import Health from "pebble/health";// Today's total (same as Health.metric.get for cumulative metrics)const today = Health.metric.query({ metric: "step count" });// Average daily steps over the past weekconst WEEK = 7 \* 24 \* 60 \* 60 \* 1000;const weeklyAverage = Health.metric.query({metric: "step count",start: Date.now() - WEEK,end: Date.now(),aggregation: "average",scope: "daily"});console.log(`Today: ${today}, daily average: ${weeklyAverage}`); ``` | Option | Values | Description | | --- | --- | --- | | `metric` | See [metric table](#reading-current-values) | The metric to query. Required. | | `start`, `end` | `Date` or milliseconds | Time range. Provide both or neither — with neither, the query covers today. | | `aggregation` | `"sum"` (default), `"average"`, `"min"`, `"max"` | How samples in the range are combined. `min`/`max` are only stored for recent history. | | `scope` | `"once"` (default), `"daily"`, `"weekly"`, `"weekday or weekend"` | Which days contribute: every day in the range at once, or averaged per-day, per-week, or by weekday/weekend. | ## Health Events Subscribe to the `"health"` event on the `watch` global to be told when new data arrives. The listener receives a string describing what changed: ``` watch.addEventListener("health", what =\> {if (what === "movement")console.log(`Steps now: ${Health.metric.get("step count")}`);}); ``` | Subtype | Meaning | | --- | --- | | `"significant"` | All data should be considered outdated (e.g. day rollover) — re-read everything | | `"movement"` | Step, distance or active-time values changed | | `"sleep"` | Sleep values changed | | `"metric"` | A [metric alert](#metric-alerts) threshold was crossed | | `"heart rate"` | A new heart rate reading is available | | `"heart rate variability"` | A new heart rate variability reading is available | ## Metric Alerts Instead of polling, register a threshold with `Health.metric.Alert`. When the metric crosses the threshold, a `"metric"` health event fires: ``` import Health from "pebble/health";const alert = new Health.metric.Alert({metric: "heart rate",threshold: 120});watch.addEventListener("health", what =\> {if (what === "metric")console.log("Heart rate above 120!");}); ``` Call `alert.close()` to cancel the alert. Alerts are also disposable, so a `using` declaration closes them automatically at the end of the scope: ``` using alert = new Health.metric.Alert({ metric: "step count", threshold: 10000 }); ``` ## Activities Pebble Health detects activities like sleeping, walking and running. `Health.activity.get()` returns a bitmask of what the user is doing right now, and `Health.activity.iterate()` walks through past (or upcoming scheduled) activity sessions: ``` import Health from "pebble/health";// What is the user doing right now?if (Health.activity.get() & Health.activity.run)console.log("Running!");// List today's walks and runsconst DAY = 24 \* 60 \* 60 \* 1000;Health.activity.iterate({activities: Health.activity.walk | Health.activity.run,start: Date.now() - DAY,end: Date.now(),callback(activity, start, end) {const minutes = Math.round((end - start) / 60000);const name = (activity === Health.activity.run) ? "Run" : "Walk";console.log(`${name}: ${minutes} minutes`);return true;// keep iterating; return false to stop}}); ``` The available activity constants are `Health.activity.sleep`, `restfulSleep`, `walk`, `run` and `openWorkout`. Use `Health.activity.accessible({activities, start, end})` to check access the same way as for metrics. ## Heart Rate Sampling By default the system samples heart rate automatically, more often during intense activity and less often at rest. An app that needs more frequent readings can request a shorter sample period (in milliseconds): ``` import Health from "pebble/health";// Request a heart rate sample every secondHealth.heartRate.samplePeriod = 1000;// ...and restore automatic sampling when doneHealth.heartRate.samplePeriod = 0; ``` The system treats the request as a suggestion, and shared use by other apps may result in a different effective rate. Always set the period back to `0` when your app no longer needs fast sampling. If the app exits without doing so, the request lingers for a while — `Health.heartRate.samplePeriodExpiration` reports how long (in milliseconds) it remains in effect. ## Minute-by-Minute History `Health.history.byMinute()` returns detailed per-minute records: ``` import Health from "pebble/health";const HOUR = 60 \* 60 \* 1000;const minutes = Health.history.byMinute({length: 60,start: Date.now() - HOUR,end: Date.now()});for (const record of minutes) {if (!record)continue;// minutes with no valid data are left undefinedconsole.log(`Steps: ${record.steps}, HR: ${record.heartRate}`);} ``` Each record has `steps`, `orientation`, `vmc` (vector magnitude counts, a measure of movement intensity), `light` (ambient light level) and `heartRate`. The returned array also carries `start` and `end` properties with the actual time range covered. ## Measurement System To display distances in the user's preferred units, ask which measurement system they chose for a metric in the mobile app: ``` const system = Health.displayMeasurementSystem("walked distance");// "metric", "imperial", or undefined if the user hasn't chosen ``` ## See Also - [_Device Info & App Lifecycle_](https://developer.repebble.com/guides/alloy/device-info.md) — the other `watch` events - [_Sensors and Input_](https://developer.repebble.com/guides/alloy/sensors-and-input.md) — live accelerometer and battery data --- # Alloy Reference Source: https://developer.repebble.com/guides/alloy/reference/ Alloy is built from three layers: the XS JavaScript engine and the Moddable SDK modules (Piu, Poco, the ECMA-419 sensor and network classes), the Pebble modules that expose the watch (`pebble/button`, `pebble/message`, `pebble/vibes` and so on), and the Pebble firmware underneath. Documentation is spread across the same three places. ## Where the Documentation Is - The guides in this section describe the Pebble modules and how to use Piu and Poco on the watch. Start with [_Getting Started with Alloy_](https://developer.repebble.com/guides/alloy/getting-started.md). - The Moddable SDK documentation at [moddable.com/documentation](https://www.moddable.com/documentation/) covers Piu, Poco (under Commodetto), the XS engine, the `manifest.json` format and the ECMA-419 classes. Alloy ships Moddable SDK 8.3; features added later are not available. - The TypeScript declarations in the SDK are the most precise reference for the Pebble modules. They are at `toolchain/moddable/typings/pebble/` inside the SDK directory (`~/Library/Application Support/Pebble SDK/SDKs/current` on macOS, `~/.pebble-sdk/SDKs/current` on Linux), one `.d.ts` file per module. - The [pebble-examples](https://github.com/Moddable-OpenSource/pebble-examples) repository has a small example for each API. Its README lists the JavaScript features that are left out of the XS build and what the "dead strip" error means. ## System Fonts Poco and Piu load the watch's built-in fonts by family name and size. Poco takes them directly: ``` const font = new render.Font("Gothic-Bold", 24); ``` Piu uses a CSS-style string. The weight becomes part of the family name, so `"bold 24px Gothic"` selects `Gothic-Bold` at 24 and `"black 30px Bitham"` selects `Bitham-Black` at 30. A string with no weight selects the `-Regular` variant. Both look the name and size up in a fixed table in the firmware. The size must match exactly; there is no scaling. If nothing matches, the constructor throws `font not found`. The table maps to the C `FONT_KEY_*` constants as follows: | Alloy family | Sizes | C constant | | --- | --- | --- | | `Gothic-Regular` | 9, 14, 18, 24, 28 | `FONT_KEY_GOTHIC_09` ... `FONT_KEY_GOTHIC_28` | | `Gothic-Bold` | 14, 18, 24, 28 | `FONT_KEY_GOTHIC_14_BOLD` ... `FONT_KEY_GOTHIC_28_BOLD` | | `Bitham-Black` | 30 | `FONT_KEY_BITHAM_30_BLACK` | | `Bitham-Bold` | 42 | `FONT_KEY_BITHAM_42_BOLD` | | `Bitham-Light` | 42, 34, 18 | `FONT_KEY_BITHAM_42_LIGHT`, `FONT_KEY_BITHAM_34_LIGHT_SUBSET`, `FONT_KEY_BITHAM_18_LIGHT_SUBSET` | | `Bitham-Medium` | 42, 34 | `FONT_KEY_BITHAM_42_MEDIUM_NUMBERS`, `FONT_KEY_BITHAM_34_MEDIUM_NUMBERS` | | `Roboto-Condensed` | 21 | `FONT_KEY_ROBOTO_CONDENSED_21` | | `Roboto-Bold` | 49 | `FONT_KEY_ROBOTO_BOLD_SUBSET_49` | | `DroidSerif-Bold` | 28 | `FONT_KEY_DROID_SERIF_28_BOLD` | | `Leco-Bold` | 20, 26, 32, 36, 38 | `FONT_KEY_LECO_20_BOLD_NUMBERS`, `FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM`, `FONT_KEY_LECO_32_BOLD_NUMBERS`, `FONT_KEY_LECO_36_BOLD_NUMBERS`, `FONT_KEY_LECO_38_BOLD_NUMBERS` | | `Leco-Regular` | 42 | `FONT_KEY_LECO_42_NUMBERS` | | `Leco-Light` | 28 | `FONT_KEY_LECO_28_LIGHT_NUMBERS` | The firmware table also lists `Gothic-Regular` and `Gothic-Bold` at size 36, which have no `FONT_KEY_*` constant in the C SDK. The Leco fonts and the `SUBSET` Bitham fonts contain digits and a few symbols only; see [_System Fonts_](https://developer.repebble.com/guides/app-resources/system-fonts.md) for the glyph sets. There is no Alloy name for `FONT_KEY_LECO_60_NUMBERS_AM_PM` or `FONT_KEY_LECO_60_BOLD_NUMBERS_AM_PM`. Fonts from the project's resources are loaded by resource ID instead of by name, with the same constructor: `new render.Font(3)`. ## Resource IDs `Poco.PebbleBitmap(id)`, `Poco.PebbleDrawCommandImage(id)` and `Poco.PebbleDrawCommandSequence(id)` take a number: the resource ID the build assigned. IDs start at 1 and follow the order of the `resources.media` array in `package.json`, counting only the resources built for the current platform: ``` "resources": {"media": [{ "type": "png", "name": "BACKGROUND", "file": "images/background.png" },{ "type": "raw", "name": "HOURS", "file": "pdc/hours.pdc" }]} ``` With this manifest `new Poco.PebbleBitmap(1)` loads `background.png` and `new Poco.PebbleDrawCommandImage(2)` loads `hours.pdc`. Inserting a resource in the middle of the list shifts every ID after it, and so do these cases: - A resource with `targetPlatforms` that excludes the current platform is skipped on that platform, so the IDs after it differ between platforms. - When `publishedMedia` is present, the build reserves ID 1 for the timeline lookup table and the first media resource becomes ID 2. - The compiled JavaScript is added as a resource named `MOD` after the project's media, and resources from Pebble Packages come after it. The build writes the final assignment to `build//src/resource_ids.auto.h` as `RESOURCE_ID_` defines. Check that file after changing `package.json` rather than counting by hand. The names in `package.json` are not available to the JavaScript at runtime. To avoid hard-coded numbers, keep one module that defines the IDs in the same order as the manifest and import it everywhere: ``` // resources.jsexport default {BACKGROUND: 1,HOURS: 2,}; ``` Piu's `Texture` and `SVGImage` classes accept either a number, which is a Pebble resource ID as above, or a string path, which is a Moddable resource listed in the `resources` section of `src/embeddedjs/manifest.json`. ## Modules and manifest.json Every JavaScript file that is imported must be listed in `src/embeddedjs/manifest.json`. The template contains: ``` {"include": ["$(MODDABLE)/examples/manifest\_mod.json","$(MODDABLE)/examples/manifest\_typings.json"],"modules": {"\*": "./main"}} ``` Under `"*"` each file becomes a module named after its file name, so `"*": ["./main", "./clock"]` makes `import Clock from "clock"` work. A key other than `"*"` gives the module a different specifier, for example `"piu/Controller": "./modules/piuController"`, and a path ending in `/*` includes every file in a directory. A `"platforms"` block selects modules per platform, with the keys `"pebble/emery"` and `"pebble/gabbro"`. Each module costs memory at startup. Prefer a few larger modules over many small ones. ## The connected Event `watch.addEventListener("connected", handler)` fires when the connection to the phone changes, in either direction. It does not fire when the listener is added, and the handler receives no argument. Read `watch.connected.app` and `watch.connected.pebblekit` for the current state, once at startup and again inside the handler: ``` function update() {console.log(`phone connected: ${watch.connected.app}`);}watch.addEventListener("connected", update);update(); ``` The firmware can take 15 to 30 seconds to report a dropped connection. ## Memory The JavaScript engine runs inside the app's RAM. By default the template's `src/c/mdbl.c` passes `NULL` to `moddable_createMachine()`, which gives the engine a 32 kB static pool that it divides between the slot heap (objects and values), the chunk heap (strings and buffers) and the stack, growing as needed. When the engine cannot allocate, the app exits and the watch shows a dialog titled _Alloy: Fatal Error_ with the engine's message, most often `memory full`. The same message appears in the app logs as `fxAbort memory full`. Other messages in that dialog are `JavaScript stack overflow`, `not enough keys` and `dead strip`, the last one meaning the code used a JavaScript feature that is left out of the XS build. Apps that need more than the default pass a `ModdableCreationRecord`: ``` #include \int main(void) {Window \*w = window\_create();window\_stack\_push(w, true);moddable\_createMachine(&(ModdableCreationRecord){.recordSize = sizeof(ModdableCreationRecord),.stack = 5120,.slot = 31744,.chunk = 19456});window\_destroy(w);return 0;} ``` All three sizes are in bytes, and either all three or none must be set. When their total exceeds 32 kB the engine uses exactly these sizes and does not grow further, so size them for the app's peak use. The watch's app RAM is 128 kB on both emery and gabbro, shared with the Pebble UI library and the native part of the app. ## TypeScript Name the source files `.ts` instead of `.js` and list them in `manifest.json` as usual. The build runs `tsc`, which must be installed and on the `PATH`; the SDK does not include it. The `manifest_typings.json` include in the template adds the Pebble and Moddable declarations to the compiler's configuration, so no `tsconfig.json` is needed. The [hellotypescript](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellotypescript) example is the smallest working project. ## Examples and SDK Versions The examples in pebble-examples target SDK 4.33.1, with these exceptions: - `piu/apps/words` and `piu/watchfaces/wallpaper` import `embedded:storage/key-value` as a module. The released firmware rejects this import, so both fail to load. Use `device.keyValue.open()` as shown in [_Storage_](https://developer.repebble.com/guides/alloy/storage.md). - `piu/watchfaces/cupertino`, `helsinki`, `redmond` and `zurich` list `flint` in `targetPlatforms`. Alloy does not run on Pebble 2 Duo, so build and install them for emery or gabbro only. - `piu/watchfaces/wallpaper` lists `gabbro` but its `manifest.json` only has modules for emery, so the gabbro build fails. It also needs a local WebSocket server to download images from. --- # Piu UI Framework Source: https://developer.repebble.com/guides/alloy/piu-guide/ Piu is a declarative UI framework for building user interfaces in Alloy apps. It provides a component-based architecture with automatic layout, styling, and animation support. > **Note** : All code in this guide runs on the watch in `src/embeddedjs/main.js`. ## Getting Started with Piu For apps using advanced Piu features (behaviors, templates, containers), include the Piu runtime: ``` import {} from "piu/MC"; ``` A basic Piu application creates an `Application` object that fills the screen: ``` const application = new Application(null, {skin: new Skin({ fill: "white" })}); ``` ## Core Concepts ### Content Objects Content objects are the basic building blocks of Piu UIs. The main types are: | Type | Description | | --- | --- | | `Content` | Basic rectangular element (base class for most UI objects) | | `Container` | Holds other content objects | | `Column` | Vertical layout container | | `Row` | Horizontal layout container | | `Label` | Single line of text | | `Text` | Multi-line formatted text | | `Application` | Root container (one per app) | ### Skins Skins define the visual appearance of content objects: ``` // Solid color skinconst whiteSkin = new Skin({ fill: "white" });// Skin with stroke/borderconst borderedSkin = new Skin({fill: "transparent",stroke: "black",borders: { left: 2, right: 2, top: 2, bottom: 2 }});// Texture-based skin (for images)const ballTexture = new Texture("balls.png");const ballSkin = new Skin({texture: ballTexture,x: 0, y: 0,width: 30, height: 30,variants: 30// for sprite sheets}); ``` ### Styles Styles define text appearance. Styles cascade, similar to CSS - you can create a base style and let child content objects inherit from it, making it easy to maintain a consistent look: ``` const textStyle = new Style({font: "OpenSans-Regular-15",color: "black",horizontal: "center",// "left", "right", "center", "justify"left: 10, right: 10,top: 15, bottom: 15});// Style with state colors (normal, active)const buttonStyle = new Style({font: "bold 18px Gothic",color: ["black", "gray"]// [normal, active]}); ``` ## Positioning and Layout Content objects are positioned using constraints: ``` // Absolute positioningContent($, { left: 20, top: 20, width: 80, height: 80 });// Anchored to edgesContent($, { right: 20, bottom: 20, width: 80, height: 80 });// Fill available spaceContent($, { left: 0, right: 0, top: 0, bottom: 0 });// Centered (no position constraints)Content($, { width: 80, height: 80 }); ``` ## Behaviors Behaviors add interactivity and logic to content objects. They are essential for handling events and updating the UI: ``` class BallBehavior extends Behavior {onCreate(ball, delta) {// delta is the data passed to the contentthis.dx = delta;this.dy = delta;}onDisplaying(ball) {// Store initial position and boundsthis.x = ball.x;this.y = ball.y;this.width = ball.container.width - ball.width;this.height = ball.container.height - ball.height;ball.start();// Start time-based updates}onTimeChanged(ball) {// Move the ballball.moveBy(this.dx, this.dy);// Update position and check boundsthis.x += this.dx;this.y += this.dy;// Bounce off wallsif (this.x \< 0 || this.x \> this.width) this.dx = -this.dx;if (this.y \< 0 || this.y \> this.height) this.dy = -this.dy;}}// Attach behavior to content - the first argument (6) is passed to onCreateContent(6, {left: 0, top: 0,width: 30, height: 30,skin: ballSkin,variant: 0,Behavior: BallBehavior}); ``` ### Common Behavior Events | Event | Description | | --- | --- | | `onCreate(content, data)` | Content created with data | | `onDisplaying(content)` | Content added to display | | `onTimeChanged(content)` | Animation frame (after `start()`) | | `onFinished(content)` | Animation/duration completed | Pebble button presses are handled using the `Button` class rather than Behavior events. See the [Sensors and Input](https://developer.repebble.com/guides/alloy/sensors-and-input.md) guide for details on button handling. Piu also uses the `Button` class internally for button input. ### Touch Events On platforms with a touchscreen (Emery, Gabbro), Piu delivers touch events to Behaviors on the content under the user's finger. Enable touch input by setting `touchCount` on the Application when it is constructed — this is the number of simultaneous contacts Piu will track (Pebble hardware supports `1`): ``` new MyApplication({}, {displayListLength: 2048,touchCount: 1,pixels: screen.width \* 4}); ``` With `touchCount: 0` (the default) Piu does not poll the touch sensor and no touch events fire. Once enabled, Behaviors receive `onTouchBegan` and `onTouchEnded` events. Hit-testing is handled by Piu — the events are delivered to the innermost active content under the touch. Set `active: true` on a container to make it receive touches: ``` class TTTSquareBehavior {onTouchBegan(container) {container.state = 1;// visual press feedback via skin variants}onTouchEnded(container) {container.state = 0;container.bubble("onTap", container);}}const TTTSquare = Container.template($ =\> ({width: 67, height: 67, skin: squareSkin, active: true,Behavior: TTTSquareBehavior,contents: [/\* ... \*/]})); ``` Use `container.bubble(event, ...args)` from inside `onTouchEnded` to forward higher-level events (tap, long-press, etc.) up the content tree, where an Application or container Behavior can handle them. | Event | Description | | --- | --- | | `onTouchBegan(content, id, x, y, ticks)` | Finger pressed on the content | | `onTouchEnded(content, id, x, y, ticks)` | Finger lifted from the content | For free-form touch input outside of Piu's hit-testing (for example, dragging in a Poco-rendered app), use the lower-level [`device.sensor.Touch`](https://developer.repebble.com/guides/alloy/sensors-and-input.md#touch) driver instead. ## Templates Templates create reusable component definitions. They are most useful when you need to create multiple instances of the same component: ``` // Define a templateconst Square = Content.template($ =\> ({width: 80, height: 80,skin: new Skin({ fill: $ })// $ is the data passed to template}));// Use the templateconst redSquare = new Square("red", { left: 20, top: 20 });const blueSquare = new Square("blue", { right: 20, bottom: 20 });application.add(redSquare);application.add(blueSquare); ``` > **Note** : For one-off content, create instances directly rather than defining a template first. Templates use more code and RAM than direct instantiation when you only need a single instance. ## Building an Application Here's a complete example with multiple components: ``` const backgroundSkin = new Skin({ fill: "silver" });const headerSkin = new Skin({ fill: "white" });const headerStyle = new Style({ font: "bold 18px Gothic", color: "black" });class HeaderBehavior extends Behavior {onDisplaying(label) {label.string = "My App";}}const application = new Application(null, {skin: backgroundSkin,contents: [new Column(null, {top: 0, bottom: 0, left: 0, right: 0,contents: [new Label(null, {top: 0, height: 30, left: 0, right: 0,skin: headerSkin,style: headerStyle,Behavior: HeaderBehavior}),new Content(null, {top: 10, bottom: 10, left: 10, right: 10,skin: new Skin({ fill: "gray" })})]})]}); ``` ## Anchors and References Use anchors to reference content objects from behaviors: ``` const MyApp = Application.template($ =\> ({contents: [Label($, {anchor: "TITLE",// Creates $.TITLE referencestring: "Hello"})],Behavior: class extends Behavior {onCreate(app, data) {this.data = data;}updateTitle(app, newTitle) {this.data.TITLE.string = newTitle;}}})); ``` ## Animations with Timeline The Timeline class creates smooth animations: ``` import Timeline from "piu/Timeline";class AnimatedBehavior extends Behavior {onDisplaying(content) {let timeline = new Timeline();// Animate 'y' property over 750mstimeline.to(content, { y: 100 }, 750, Math.quadEaseOut, 0);content.duration = timeline.duration;timeline.seekTo(0);content.time = 0;content.start();}onTimeChanged(content) {this.timeline.seekTo(content.time);}} ``` ### Easing Functions Available easing functions on the `Math` object: - `Math.backEaseIn` / `Math.backEaseOut` - `Math.bounceEaseIn` / `Math.bounceEaseOut` - `Math.circularEaseIn` / `Math.circularEaseOut` - `Math.cubicEaseIn` / `Math.cubicEaseOut` - `Math.elasticEaseIn` / `Math.elasticEaseOut` - `Math.exponentialEaseIn` / `Math.exponentialEaseOut` - `Math.quadEaseIn` / `Math.quadEaseOut` - `Math.quartEaseIn` / `Math.quartEaseOut` - `Math.quintEaseIn` / `Math.quintEaseOut` - `Math.sineEaseIn` / `Math.sineEaseOut` For more animation techniques including chaining and looping, see the [Animations](https://developer.repebble.com/guides/alloy/animations.md) guide. ## Displaying Images Load and display images using Texture and Skin: ``` // Load a texture from resourcesconst logoTexture = new Texture("logo.png");// Create a skin from the textureconst logoSkin = new Skin({texture: logoTexture,x: 0, y: 0,width: 64, height: 64});// Display the imageapplication.add(new Content(null, {skin: logoSkin})); ``` ## Text Display ### Single Line (Label) ``` application.add(new Label(null, {left: 0, right: 0, top: 50,style: new Style({ font: "bold 24px Gothic", color: "black" }),string: "Hello, World!"})); ``` ### Multi-line (Text) ``` application.add(new Text(null, {left: 10, right: 10, top: 10,style: textStyle,blocks: [{ spans: "First paragraph of text." },{ spans: ["Second paragraph with ",{ style: boldStyle, spans: "bold text" }," inline."]}]})); ``` ## Using Pebble Fonts Access built-in Pebble fonts with CSS-like syntax: ``` // Format: "[style] [size]px [family]"const headerStyle = new Style({font: "bold 14px Gothic",color: "black"});const titleStyle = new Style({font: "black 30px Bitham",color: "blue"});const bodyStyle = new Style({font: "24px Leco",color: "white"}); ``` Available Pebble fonts include: - `Gothic` - Regular and Bold weights - `Bitham` - Black and Bold weights - `Roboto` - Condensed weight - `Leco` - Regular weight (great for numbers) - `Droid` - Serif style The available sizes are limited for each font family. See the [System Fonts](https://developer.repebble.com/guides/app-resources/system-fonts.md) reference for the full table of all fonts, styles, and sizes. ## Loading Bitmaps from Resources Load images from Pebble resources by ID: ``` // Load texture by resource IDconst backgroundTexture = new Texture(2);// Resource ID 2// Create a tiled background skinconst backgroundSkin = new Skin({texture: backgroundTexture,x: 0, y: 0,width: 40, height: 40,tiles: { left: 0, right: 0, top: 0, bottom: 0 }});// Load and display an imageconst iconTexture = new Texture(3);application.add(new Content(null, {skin: new Skin({texture: iconTexture,width: iconTexture.width,height: iconTexture.height})})); ``` ## PDC (Pebble Draw Command) Images PDC images are vector graphics converted from SVG. In Piu, use the `SVGImage` class to display and transform PDC files. Place `.pdc` files in your `src/embeddedjs/assets/` directory and reference them by filename: ``` const application = new Application(null, {skin: new Skin({ fill: "gray" }),contents: [SVGImage(null, { path: "icon.pdc" })]}); ``` The `path` property specifies the `.pdc` filename from your assets directory. ### SVGImage Transforms SVGImage supports rotation, scaling, and translation through built-in properties: | Property | Description | | --- | --- | | `r` | Rotation angle in radians | | `s` | Uniform scale factor | | `sx`, `sy` | Non-uniform scale (horizontal, vertical) | | `tx`, `ty` | Translation offset (x, y) | | `cx`, `cy` | Center of rotation | In the [gravity example](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/gravity), a PDC star is continuously rotated and moved based on accelerometer readings. The rotation portion of its Behavior updates the `r` property each frame: ``` let degree = this.degree + 1;if (degree \> 360)degree -= 360;this.degree = degree;star.r = (this.degree / 180) \* Math.PI; ``` Unlike Poco's `clone().rotate()` approach, Piu transforms the SVGImage in place through its properties - no cloning needed. ### Animated SVGImage with Timeline Use `duration`, `fraction`, and `start()` to animate SVGImage transforms with easing: ``` const graySkin = new Skin({ fill: "gray" });class ImageBehavior extends Behavior {onAnimate(image) {image.duration = 1500;image.time = 0;image.start();}onDisplaying(image) {this.delta = (image.container.width \>\> 1) + image.width;image.tx = this.delta;}onFinished(image) {image.r = 0;image.tx = this.delta;let next = image.next ?? image.container.first;next.delegate("onAnimate");}onTimeChanged(image) {let fraction = image.fraction;if (fraction \< 0.4) {fraction = Math.quadEaseOut(0.4 - fraction);image.r = fraction \* Math.PI;image.tx = this.delta \* fraction;}else if (fraction \> 0.6) {fraction = Math.quadEaseOut(0.6 - fraction);image.r = fraction \* Math.PI;image.tx = this.delta \* fraction;}else {image.r = 0;image.tx = 0;}}}class TestApplicationBehavior {onDisplaying(application) {application.duration = 1000;application.first.delegate("onAnimate");}}const TestApplication = Application.template($ =\> ({Behavior: TestApplicationBehavior, skin: graySkin,contents: [SVGImage(6, { path: "Pebble\_80x80\_Incoming\_call\_centered.pdc", Behavior: ImageBehavior }),SVGImage(5, { path: "Pebble\_80x80\_Scheduled\_event.pdc", Behavior: ImageBehavior }),SVGImage(4, { path: "Pebble\_80x80\_Outgoing\_call.pdc", Behavior: ImageBehavior }),]}));export default new TestApplication({}, {displayListLength: 4096, touchCount: 0, pixels: screen.width \* 4,}); ``` The `fraction` property returns a value from 0 to 1 representing progress through the `duration`. Combine it with easing functions from `Math` for smooth motion. ### PDC Sequences PDC sequences are animated vector graphics containing multiple frames. Use `SVGImage` with a sequence `.pdc` file and control playback through Behavior events: ``` const graySkin = new Skin({ fill: "gray" });class ClockBehavior extends Behavior {onDisplaying(image) {image.start();}onFinished(image) {image.time = 0;image.start();}onTimeChanged(image) {let fraction = image.fraction;if (fraction \< 0.5)fraction = 1 + fraction;elsefraction = 2 - fraction;image.sx = image.sy = fraction;}}class TestApplicationBehavior {}const TestApplication = Application.template($ =\> ({Behavior: TestApplicationBehavior, skin: graySkin,contents: [SVGImage($, { bottom: 20, path: "clock\_sequence.pdc", Behavior: ClockBehavior })]}));export default new TestApplication({}, {displayListLength: 2048, touchCount: 0, pixels: screen.width \* 4,}); ``` ### Clock Hands with SVGImage A common pattern is using SVGImage for watchface clock hands. Each hand is a separate PDC file, rotated based on the current time: ``` const scale = Math.min(screen.width, screen.height) / 240;class FaceHandBehavior {onFractionChanged(content, fraction) {const angle = ((-fraction \* 2) - 1) \* Math.PI;content.r = angle;}onClockResized(content) {const container = content.container;content.x = (container.width \>\> 1) - content.cx;content.y = (container.height \>\> 1) - content.cy;}}class FaceHoursBehavior extends FaceHandBehavior {onDisplaying(content) {content.cx = 7;content.cy = 14;content.s = scale;this.onClockResized(content);}onClockChanged(content, clock) {this.onFractionChanged(content,(clock.hours % 12 + clock.minutes / 60) / 12);}}class FaceMinutesBehavior extends FaceHandBehavior {onDisplaying(content) {content.cx = 5;content.cy = 20;content.s = scale;this.onClockResized(content);}onClockChanged(content, clock) {this.onFractionChanged(content, clock.minutes / 60);}} ``` The `cx` and `cy` properties set the rotation center, which should match the pivot point of the clock hand in the original SVG. The `scale` factor adapts the hand size to the screen. In the application template, layer the hands from bottom to top: ``` class FaceApplicationBehavior {onDisplaying(application) {watch.addEventListener("secondchange", (clock) =\> {const date = clock.date;application.distribute("onClockChanged", {date,hours: date.getHours(),minutes: date.getMinutes(),seconds: date.getSeconds(),});});}onResize(application) {application.distribute("onClockResized");}}const FaceApplication = Application.template($ =\> ({Behavior: FaceApplicationBehavior,contents: [Content($, { skin: new Skin({ texture: new Texture("dial.png"),width: screen.width, height: screen.height }) }),SVGImage($, { left: 0, width: 14, top: 0, height: 79,path: "hours.pdc", Behavior: FaceHoursBehavior }),SVGImage($, { left: 0, width: 10, top: 0, height: 100,path: "minutes.pdc", Behavior: FaceMinutesBehavior }),]}));export default new FaceApplication(null, {displayListLength: 2048, touchCount: 0, pixels: screen.width \* 4,}); ``` Use `distribute()` to send clock updates to all hands simultaneously. ## Complete Example: Bouncing Balls Here's a complete animated app with multiple bouncing balls: ``` import {} from "piu/MC";const backgroundSkin = new Skin({ fill: "silver" });const ballTexture = new Texture("balls.png");const ballSkin = new Skin({texture: ballTexture,x: 0, y: 0,width: 30, height: 30,variants: 30// sprite sheet with 30px variants});class BallBehavior extends Behavior {onCreate(ball, delta) {this.dx = delta;this.dy = delta;}onDisplaying(ball) {this.x = ball.x;this.y = ball.y;this.width = ball.container.width - ball.width;this.height = ball.container.height - ball.height;ball.start();}onTimeChanged(ball) {ball.moveBy(this.dx, this.dy);this.x += this.dx;this.y += this.dy;if (this.x \< 0 || this.x \> this.width) this.dx = -this.dx;if (this.y \< 0 || this.y \> this.height) this.dy = -this.dy;}}const BallApplication = Application.template($ =\> ({skin: backgroundSkin,contents: [Content(6, { left: 0, top: 0, skin: ballSkin, variant: 0, Behavior: BallBehavior }),Content(5, { right: 0, top: 0, skin: ballSkin, variant: 1, Behavior: BallBehavior }),Content(4, { right: 0, bottom: 0, skin: ballSkin, variant: 2, Behavior: BallBehavior }),Content(3, { left: 0, bottom: 0, skin: ballSkin, variant: 3, Behavior: BallBehavior }),]}));export default new BallApplication(null, { pixels: screen.width \* 4 }); ``` ## Related Guides - [Animations](https://developer.repebble.com/guides/alloy/animations.md) - Advanced animation techniques with Timeline and easing functions - [Port (Custom Drawing)](https://developer.repebble.com/guides/alloy/port-drawing.md) - Combine Piu layout with custom Poco-style drawing - [Poco Graphics](https://developer.repebble.com/guides/alloy/poco-guide.md) - Low-level graphics alternative to Piu - [Converting SVG to PDC](https://developer.repebble.com/guides/app-resources/converting-svg-to-pdc.md) - How to create PDC files from SVG - [Vector Graphics](https://developer.repebble.com/guides/graphics-and-animations/vector-graphics.md) - Overview of vector graphics on Pebble ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes several Piu examples: - [`hellopiu-balls`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-balls) - animated bouncing balls with Behavior and texture variants - [`hellopiu-coloredsquares`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-coloredsquares) - basic layout with colored skins - [`hellopiu-gbitmap`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-gbitmap) - displaying Pebble GBitmap PNG images - [`hellopiu-jsicon`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-jsicon) - displaying a Moddable SDK bitmap from a PNG resource - [`hellopiu-text`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-text) - dynamic text layout with different fonts and alignment - [`hellopiu-pebbletext`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-pebbletext) - text rendering with Pebble's built-in fonts - [`piu/apps/pdc-images`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/pdc-images) - moving and rotating PDC (SVG) images - [`piu/apps/pdc-sequence`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/pdc-sequence) - playback of animated PDC sequences - [`piu/apps/compass`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/compass) - compass visualization with rotating PDC image - [`piu/apps/gravity`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/gravity) - accelerometer-driven PDC star animation - [`piu/apps/tic-tac-toe`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/tic-tac-toe) - touch input via `onTouchBegan`/`onTouchEnded` Behavior events - [`piu/watchfaces/minato`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/minato) - watchface with PDC clock hands --- # Poco Graphics Source: https://developer.repebble.com/guides/alloy/poco-guide/ Poco is a low-level graphics rendering framework that gives you direct control over pixel drawing. It's ideal for custom graphics, games, and watchfaces where you need precise control over rendering. > **Note** : All code in this guide runs on the watch in `src/embeddedjs/main.js`. ## Getting Started Import Poco and create a renderer instance: ``` import Poco from "commodetto/Poco";const render = new Poco(screen); ``` The `screen` global provides access to the Pebble display. ## Basic Drawing All drawing happens between `begin()` and `end()` calls: ``` render.begin();// Drawing commands hererender.end(); ``` You can also specify a clip region to update only part of the screen: ``` render.begin(x, y, width, height);// Only draws within the specified rectanglerender.end(); ``` ## Colors Create colors using `makeColor()` with RGB values (0-255): ``` const black = render.makeColor(0, 0, 0);const white = render.makeColor(255, 255, 255);const red = render.makeColor(255, 0, 0);const gray = render.makeColor(128, 128, 128);const lightBlue = render.makeColor(0x80, 0xc0, 0xff); ``` ## Drawing Shapes ### Rectangles ``` // Filled rectanglerender.fillRectangle(color, x, y, width, height);// Example: fill the entire screen whiterender.fillRectangle(white, 0, 0, render.width, render.height); ``` ### Rounded Rectangles ``` // Rounded rectangle with radius// corners is a bitmask: 0b0001=TL, 0b0010=TR, 0b0100=BL, 0b1000=BRrender.drawRoundRect(x, y, width, height, color, radius, corners);// All corners roundedrender.drawRoundRect(10, 10, 100, 50, black, 8);// Only top corners roundedrender.drawRoundRect(10, 10, 100, 50, black, 8, 0b0011);// Frame (outline) of rounded rectanglerender.frameRoundRect(x, y, width, height, color, radius); ``` ### Lines ``` // Draw a line with specified thicknessrender.drawLine(x1, y1, x2, y2, color, thickness);// Example: diagonal linesrender.drawLine(0, 0, render.width, render.height, gray, 4);render.drawLine(0, render.height, render.width, 0, gray, 4); ``` ### Circles ``` // Draw a circle or arc// startAngle and endAngle are in degreesrender.drawCircle(color, centerX, centerY, radius, startAngle, endAngle);// Full circlerender.drawCircle(black, 90, 90, 30, 0, 360);// Arc (quarter circle)render.drawCircle(blue, 90, 90, 30, 0, 90); ``` ## Text Rendering ### Using Pebble Built-in Fonts ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const font = new render.Font("Bitham-Black", 30);const black = render.makeColor(0, 0, 0);render.begin();render.drawText("Hello!", font, black, 10, 50);render.end(); ``` Available Pebble fonts: - `Gothic-Regular` (various sizes) - `Gothic-Bold` - `Bitham-Black` - `Bitham-Bold` - `Roboto-Condensed` - `Leco-Regular` - `Droid-Serif` The available sizes are limited for each font family. See the [System Fonts](https://developer.repebble.com/guides/app-resources/system-fonts.md) reference for the full table of all fonts, styles, and sizes. ### Centering Text Use `getTextWidth()` to measure text for centering: ``` const msg = "Hello!";const width = render.getTextWidth(msg, font);const x = (render.width - width) / 2;const y = (render.height - font.height) / 2;render.drawText(msg, font, black, x, y); ``` ### Using Custom Fonts (Moddable SDK Fonts) For custom fonts bundled with your app, use `parseBMF` and `parseRLE`: ``` import Poco from "commodetto/Poco";import parseBMF from "commodetto/parseBMF";import parseRLE from "commodetto/parseRLE";const render = new Poco(screen);const black = render.makeColor(0, 0, 0);// Load custom font from resourcesfunction getFont(name, size) {const font = parseBMF(new Resource(`${name}-${size}.fnt`));font.bitmap = parseRLE(new Resource(`${name}-${size}-alpha.bm4`));return font;}const openSans = getFont("OpenSans-Regular", 21);const noto = getFont("NotoSansJP-Regular", 24);render.begin();render.drawText("Hello!", openSans, black, 10, 10);render.drawText("新宿出口", noto, black, 10, 40);// Japanese textrender.end(); ``` Custom fonts allow Unicode support and unique typography not available in built-in Pebble fonts. ## Drawing Images ### Bitmaps Load bitmaps from resources using `PebbleBitmap`: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);// Load bitmap by resource IDconst bitmap = new Poco.PebbleBitmap(2);render.begin();// Center the bitmapconst x = (render.width - bitmap.width) / 2;const y = (render.height - bitmap.height) / 2;render.drawBitmap(bitmap, x, y);render.end(); ``` Bitmaps have `width` and `height` properties for positioning. ### PDC (Pebble Draw Command) Images PDC images are vector graphics converted from SVG: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);// Load PDC image by resource IDconst pdc = new Poco.PebbleDrawCommandImage(2);render.begin();// Center the PDC imageconst x = (render.width - pdc.width) / 2;const y = (render.height - pdc.height) / 2;render.drawDCI(pdc, x, y);render.end(); ``` ### PDC Sequences (Animations) PDC sequences are animated vector graphics. Use the `time` property to advance through frames: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const gray = render.makeColor(128, 128, 128);const dcs = new Poco.PebbleDrawCommandSequence(2);console.log("Sequence dimensions: " + dcs.width + " x " + dcs.height);console.log("Sequence duration: " + dcs.duration + " ms");function draw() {render.begin();render.fillRectangle(gray, 0, 0, render.width, render.height);render.drawDCI(dcs,(render.width - dcs.width) / 2,(render.height - dcs.height) / 2);render.end();// Advance to next frameconst frameDuration = dcs.frameDuration;dcs.time += frameDuration;setTimeout(draw, frameDuration);}draw(); ``` PDC sequences have these properties: - `width`, `height` - Dimensions of the sequence - `duration` - Total animation duration in milliseconds - `frameDuration` - Duration of current frame - `time` - Current playback position (set this to advance) ## Animation Use `setInterval()` for frame-based animations. The interval is a request - if Pebble OS can't render fast enough, the actual frame rate will be lower. Smoothest results come from requesting a frame rate that can be consistently achieved: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const white = render.makeColor(255, 255, 255);const black = render.makeColor(0, 0, 0);let x = 0;setInterval(() =\> {render.begin();// Clear screenrender.fillRectangle(white, 0, 0, render.width, render.height);// Draw moving circlerender.drawCircle(black, x, 90, 20, 0, 360);render.end();x = (x + 2) % render.width;}, 50);// ~20 fps ``` ### Rotating PDC Images PDC images can be transformed. Use `clone()` and `rotate()` for rotation: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const gray = render.makeColor(128, 128, 128);const dci = new Poco.PebbleDrawCommandImage(2);console.log("PDC dimensions: " + dci.width + " x " + dci.height);let angle = 0;setInterval(() =\> {render.begin();render.fillRectangle(gray, 0, 0, render.width, render.height);// Clone, rotate around center, then drawrender.drawDCI(dci.clone().rotate(angle, dci.width / 2, dci.height / 2),(render.width - dci.width) / 2,(render.height - dci.height) / 2);angle += Math.PI / 30;render.end();}, 50);// ~20fps ``` The `rotate()` method takes angle in radians and pivot point coordinates. ### Scaling PDC Images PDC images can also be scaled dynamically: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const gray = render.makeColor(128, 128, 128);const dci = new Poco.PebbleDrawCommandImage(2);console.log("PDC size: " + dci.width + " x " + dci.height);const start = Date.now();setInterval(() =\> {// Animate scale with elastic easingconst progress = ((Date.now() - start) % 1500) / 1500;const scale = Math.elasticEaseOut(progress);// Center the scaled imageconst cx = (render.width - dci.width \* scale) / 2;const cy = (render.height - dci.height \* scale) / 2;render.begin();render.fillRectangle(gray, 0, 0, render.width, render.height);render.drawDCI(dci.clone().scale(scale), cx, cy);render.end();}, 50); ``` The `scale()` method takes a scale factor (1.0 = original size). ### PDC Transformation Methods | Method | Description | | --- | --- | | `clone()` | Create a copy for transformation | | `rotate(angle, pivotX, pivotY)` | Rotate by angle (radians) around pivot | | `scale(factor)` | Scale uniformly by factor | Always call `clone()` before transforming to preserve the original. ## Complete Example: Animated Display ``` import Poco from "commodetto/Poco";console.log("Starting animation demo");const render = new Poco(screen);const black = render.makeColor(0, 0, 0);const white = render.makeColor(255, 255, 255);const gray = render.makeColor(128, 128, 128);let angle = 0;setInterval(() =\> {render.begin(0, 0, render.width, render.height);// Backgroundrender.fillRectangle(white, 0, 0, render.width, render.height);// Diagonal linesrender.drawLine(0, 0, render.width, render.height, gray, 4);render.drawLine(0, render.height, render.width, 0, gray, 4);// Rounded rectangle frameconst margin = 30;render.drawRoundRect(margin, margin,render.width - (margin \* 2),render.height - (margin \* 2),black, 8);// Rotating arcconst cx = render.width / 2;const cy = render.height / 2;render.drawCircle(gray, cx, cy, 15, angle, angle + 270);render.end();angle = (angle + 10) % 360;}, 50); ``` ## Screen Properties The `render` object provides useful screen information: | Property | Description | | --- | --- | | `render.width` | Screen width in pixels | | `render.height` | Screen height in pixels | ## Performance Tips 1. **Minimize draw area** : Use `begin(x, y, w, h)` to only redraw changed regions 2. **Batch drawing** : Do all drawing between a single `begin()`/`end()` pair 3. **Reuse colors** : Create colors once and store them in variables 4. **Preload fonts** : Create font objects once at startup 5. **Use PDC for complex graphics** : Vector graphics scale better and use less memory than bitmaps ## Poco vs Piu | Feature | Poco | Piu | | --- | --- | --- | | Approach | Procedural | Declarative | | Control | Low-level, pixel-perfect | High-level, automatic layout | | Best for | Games, watchfaces, custom graphics | Apps with standard UI patterns | | Learning curve | Simpler concepts | More abstraction to learn | | Performance | More control over optimization | Framework handles optimization | Use Poco when you need precise control over every pixel. Use Piu when you want automatic layout and a component-based architecture. ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes several Poco examples: - [`hellopoco-gbitmap`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-gbitmap) - rendering Pebble GBitmap bitmaps - [`hellopoco-text`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-text) - text rendering with Moddable SDK fonts - [`hellopoco-pebbletext`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pebbletext) - text rendering with Pebble's built-in fonts - [`hellopoco-pebblegraphics`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pebblegraphics) - lines, rounded rectangles, and circles - [`hellopoco-pdc`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc) - rendering PDC (Draw Command) SVG images - [`hellopoco-pdc-rotate`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc-rotate) - rotating a PDC image - [`hellopoco-pdc-scale`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc-scale) - scaling a PDC image with easing - [`hellopoco-pdc-sequence`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc-sequence) - animated PDC image sequences --- # Sensors and Input Source: https://developer.repebble.com/guides/alloy/sensors-and-input/ Alloy provides access to Pebble hardware sensors and button input through simple JavaScript APIs. Sensors follow the ECMA-419 Sensor Class Pattern, while the Button class is a Pebble-specific API. > **Note** : All code in this guide runs on the watch in `src/embeddedjs/main.js`. ## Button Input Handle button presses using the `Button` class: ``` import Button from "pebble/button";new Button({types: ["select", "up", "down", "back"],onPush(down, type) {console.log((down ? "press " : "release ") + type);}}); ``` > **Note** : If your app includes `"back"` in the `types` array, the back button no longer exits the app automatically - press and hold back to exit instead. If you don't include `"back"`, pressing back exits as usual. ### Button Types | Type | Description | | --- | --- | | `"select"` | Middle button (center right) | | `"up"` | Top button | | `"down"` | Bottom button | | `"back"` | Back button (left side) | ### Click Recognizers By default a `Button` reports _raw_ presses and releases, as above. Since SDK 4.33, buttons can instead (or additionally) use the system's click recognizers, which take care of repeat-while-held, long presses and multi-clicks for you. Enable them with extra options in the constructor: ``` import Button from "pebble/button";new Button({types: ["select", "up", "down"],single: { repeat: 200 },// clicks, repeating every 200ms while heldlong: { delay: 700 },// long presses after 700msonPush(down, type, recognizer, count, repeat) {if (recognizer === "long") {console.log(down ? ("long press " + type) : "long release");} else if (down) {console.log("click " + type + (repeat ? " (held)" : ""));}}}); ``` | Option | Value | Behavior | | --- | --- | --- | | `single` | `true` or `{ repeat }` | Fires once per click with `down = 1`. With a `repeat` interval (ms), the click fires repeatedly while the button is held, with `repeat = true` on repeats. | | `long` | `true` or `{ delay }` | Fires with `down = 1` once the button has been held for `delay` ms (default is the system delay), and again with `down = 0` on release. | | `multi` | `true` or `{ min, max, lastOnly, timeout }` | Recognizes sequences of `min` to `max` clicks arriving within `timeout` ms of each other. Fires with `down = 1` and `count` set to the number of clicks; with `lastOnly` it fires only for the final count. | | `raw` | `true` | Plain press (`down = 1`) and release (`down = 0`) events. This is the default when no recognizer option is given. | Recognizer options apply to all buttons in the `types` array, and multiple recognizers can be combined on the same button. Two restrictions apply to the back button: it does not support `long` (the system reserves press-and-hold back for exiting the app), and its `raw` events are emulated, delivering the press and release back-to-back on click. ### Button Events The `onPush` callback receives five parameters: - `down` - `1` when pressed, `0` when released (use it as a truthy/falsy value) - `type` - String: which button was pressed - `recognizer` - String: which recognizer fired (`"single"`, `"long"`, `"multi"` or `"raw"`) - `count` - Number of clicks counted (e.g. for `multi` sequences) - `repeat` - `true` when this is a repeating `single` click from holding the button Callbacks that only declare `(down, type)` keep working unchanged. ## Accelerometer Read motion data from the accelerometer: ``` import Accelerometer from "embedded:sensor/Accelerometer";const accel = new Accelerometer({onSample() {const sample = this.sample();console.log("accel " + sample.x + ", " + sample.y + ", " + sample.z);},onTap(direction) {console.log("single tap " + direction);},onDoubleTap(direction) {console.log("double tap " + direction);}});// Configure sample rate (Hz)accel.configure({ hz: 10 }); ``` ### Accelerometer Sample Data The `sample()` method returns an object with: | Property | Description | | --- | --- | | `x` | Acceleration on X axis | | `y` | Acceleration on Y axis | | `z` | Acceleration on Z axis | ### Accelerometer Events | Event | Description | | --- | --- | | `onSample()` | Called when new sensor data is available | | `onTap(direction)` | Called on single tap gesture | | `onDoubleTap(direction)` | Called on double tap gesture | ### Configuration Options ``` accel.configure({hz: 10// Sample rate in Hz (samples per second)}); ``` ## Compass Read the magnetic heading from the compass: ``` import Compass from "embedded:sensor/Compass";const compass = new Compass({onSample() {const sample = this.sample();console.log("heading " + sample.heading);}}); ``` ### Compass Sample Data | Property | Description | | --- | --- | | `heading` | Magnetic heading in degrees (0-360) | ## Location Get the user's GPS location via the phone: ``` import Location from "embedded:sensor/Location";const location = new Location({onSample() {const sample = this.sample();console.log("Location: " + sample.latitude + ", " + sample.longitude);this.close();}}); ``` The Location sensor follows the same ECMA-419 pattern as other sensors but has a few differences: - **One-shot** - call `this.close()` after receiving the sample. Location is a request, not a continuous monitor. - **Requires the phone** - location data comes from the phone's GPS via the `@moddable/pebbleproxy` package. - **Requires capability** - add `"location"` to the `capabilities` array in `package.json`. ### Location Sample Data | Property | Type | Description | | --- | --- | --- | | `latitude` | Number | Latitude in decimal degrees | | `longitude` | Number | Longitude in decimal degrees | ### Configuration Options You can optionally configure the location request immediately after creating the instance: ``` location.configure({enableHighAccuracy: false,timeout: 5000,maximumAge: 0}); ``` | Option | Description | | --- | --- | | `enableHighAccuracy` | Request high-accuracy GPS (uses more battery) | | `timeout` | Maximum time in ms to wait for a location | | `maximumAge` | Accept a cached location up to this many ms old | ### PKJS Setup for Location The Location sensor requires the `@moddable/pebbleproxy` package. Set up `src/pkjs/index.js`: ``` const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', moddableProxy.readyReceived);Pebble.addEventListener('appmessage', moddableProxy.appMessageReceived); ``` No custom location code is needed in PKJS - the proxy handles GPS lookup automatically. ## Touch On platforms with a touchscreen (Emery, Gabbro), read raw touch points using the `Touch` sensor exposed as `device.sensor.Touch`: ``` const touch = new device.sensor.Touch({onSample() {const points = this.sample();if (!points) return;if (points.length === 0) {console.log("lift-off");return;}const { x, y } = points[0];console.log("touch " + x + ", " + y);}}); ``` ### Touch Sample Data `sample()` returns either a falsy value (no new data since the last call) or an array of current contact points. An empty array signals that the last finger has lifted off the screen. | Property | Description | | --- | --- | | `x` | X coordinate in screen pixels | | `y` | Y coordinate in screen pixels | ### Tracking Touch State `onSample` only tells you the current set of contact points, not when a touch started or ended. Track that state yourself to get began/moved/ended semantics: ``` const touch = new device.sensor.Touch({onSample() {const points = this.sample();if (!points) return;const last = this.points[0];if (points.length === 0) {if (last) {console.log("ended at " + last.x + ", " + last.y);this.points[0] = undefined;}return;}const { x, y } = points[0];if (last) {last.x = x;last.y = y;console.log("moved to " + x + ", " + y);} else {this.points[0] = { x, y };console.log("began at " + x + ", " + y);}}});touch.points = new Array(1); ``` > **Note** : For Piu apps, you generally don't need this sensor directly - Piu delivers `onTouchBegan`/`onTouchEnded` events to container Behaviors when `touchCount` is set on the Application. See the [Piu guide](https://developer.repebble.com/guides/alloy/piu-guide.md) for details. ## Battery Status Monitor battery level and charging state: ``` import Battery from "embedded:sensor/Battery";const battery = new Battery({onSample() {const sample = this.sample();console.log("battery " + sample.percent + "%, charging " +sample.charging + ", plugged " + sample.plugged);}});// Get current state immediatelyconst status = battery.sample();console.log("battery " + status.percent + "%"); ``` ### Battery Sample Data | Property | Type | Description | | --- | --- | --- | | `percent` | Number | Battery level (0-100) | | `charging` | Boolean | Whether battery is charging | | `plugged` | Boolean | Whether charger is connected | ## Connection Status Monitor connection to the phone: ``` function logConnected() {console.log("App connected: " + watch.connected.app);console.log("PebbleKitJS connected: " + watch.connected.pebblekit);}watch.addEventListener('connected', logConnected);logConnected(); ``` The `watch.connected` object has two properties: | Property | Description | | --- | --- | | `app` | Whether the Pebble app is connected | | `pebblekit` | Whether PebbleKit JS is ready for messaging | ## Complete Example: Sensor Dashboard Here's a complete example that displays sensor data: ``` import Poco from "commodetto/Poco";import Button from "pebble/button";import Accelerometer from "embedded:sensor/Accelerometer";import Battery from "embedded:sensor/Battery";const render = new Poco(screen);const font = new render.Font("Gothic-Regular", 18);const black = render.makeColor(0, 0, 0);const white = render.makeColor(255, 255, 255);let accelData = { x: 0, y: 0, z: 0 };let batteryPercent = 0;let lastButton = "none";// Setup accelerometerconst accel = new Accelerometer({onSample() {accelData = this.sample();draw();}});accel.configure({ hz: 10 });// Setup battery monitorconst battery = new Battery({onSample() {batteryPercent = this.sample().percent;draw();}});batteryPercent = battery.sample().percent;// Setup button handlernew Button({types: ["select", "up", "down"],onPush(down, type) {if (down) {lastButton = type;draw();}}});function draw() {render.begin();render.fillRectangle(white, 0, 0, render.width, render.height);render.drawText("Battery: " + batteryPercent + "%", font, black, 10, 10);render.drawText("Button: " + lastButton, font, black, 10, 35);render.drawText("Accel X: " + accelData.x.toFixed(2), font, black, 10, 60);render.drawText("Accel Y: " + accelData.y.toFixed(2), font, black, 10, 85);render.drawText("Accel Z: " + accelData.z.toFixed(2), font, black, 10, 110);render.end();}draw(); ``` ## Testing in the Emulator When using the Pebble emulator, you can simulate sensor input: ### Battery ``` $ pebble emu-battery --percent 20 --charging --qemu localhost:12344 ``` ### Accelerometer ``` $ pebble emu-accel tilt-left --qemu localhost:12344 ``` ## Best Practices 1. **Clean up resources** : Sensors continue to consume power while active. Stop them when not needed. 2. **Appropriate sample rates** : Higher accelerometer sample rates use more power. Use the lowest rate that meets your needs. 3. **Handle disconnection gracefully** : Check connection status before attempting network operations. ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes sensor and input examples: - [`hellobutton`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellobutton) - subscribing to button press and release events - [`helloaccelerometer`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloaccelerometer) - reading accelerometer data and detecting taps - [`hellobattery`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellobattery) - monitoring battery level and charging state - [`hellolocation`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellolocation) - getting GPS location from the phone - [`hellopoco-drag`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-drag) - draggable UI built on the `device.sensor.Touch` driver - [`piu/apps/gravity`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/gravity) - visualizes accelerometer readings with an animated display - [`piu/apps/compass`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/compass) - visualizes compass readings with a rotating compass rose --- # Storage Source: https://developer.repebble.com/guides/alloy/storage/ Alloy provides several ways to persist data on the watch: the Web standard `localStorage` API, the ECMA-419 Key-Value Storage API, and a file system API. > **Note** : All code in this guide runs on the watch in `src/embeddedjs/main.js`. ## localStorage (Recommended) The `localStorage` API provides a simple, familiar way to store string data: **⌚ Watch** : ``` // Store a valuelocalStorage.setItem("username", "Alice");// Retrieve a valueconst username = localStorage.getItem("username");console.log("username: " + username);// "Alice"// Remove a valuelocalStorage.removeItem("username"); ``` ### localStorage Methods | Method | Description | | --- | --- | | `setItem(key, value)` | Store a value | | `getItem(key)` | Retrieve a value (returns `null` if not found) | | `removeItem(key)` | Delete a value | | `clear()` | Remove all stored values | ### Example: Persistent Counter ``` // Get current count or start at 0let counter = localStorage.getItem("counter");if (counter === null) {console.log("initializing counter");counter = 1;} else {counter = Number(counter) + 1;}if (counter \< 5) {console.log("save counter value " + counter);localStorage.setItem("counter", counter);} else {console.log("reset counter");localStorage.removeItem("counter");} ``` ### Storing Objects Since `localStorage` only stores strings, serialize objects with JSON: ``` // Store an objectconst settings = {theme: "dark",notifications: true,fontSize: 14};localStorage.setItem("settings", JSON.stringify(settings));// Retrieve the objectconst stored = localStorage.getItem("settings");if (stored) {const settings = JSON.parse(stored);console.log("theme: " + settings.theme);// "dark"} ``` ## Key-Value Storage (ECMA-419) For more control, and to be able to store binary data as well as strings, use the ECMA-419 Key-Value Storage API: ``` // Open a storage fileconst store = device.keyValue.open({path: "mysettings",format: "string"});// Write a valuestore.write("counter", "42");// Read a valueconst value = store.read("counter");console.log("value: " + value);// "42"// Delete a valuestore.delete("counter");// Close when donestore.close(); ``` ### device.keyValue.open() Options | Option | Description | | --- | --- | | `path` | Name of the storage file (required) | | `format` | Data format: `"string"` or `"buffer"` | ### Store Methods | Method | Description | | --- | --- | | `write(key, value)` | Store a value | | `read(key)` | Retrieve a value (returns `undefined` if not found) | | `delete(key)` | Remove a value | | `close()` | Close the storage file | ### Example: Settings with Key-Value Storage ``` const store = device.keyValue.open({ path: "appsettings", format: "string" });// Save settingsfunction saveSettings(settings) {store.write("settings", JSON.stringify(settings));}// Load settings with defaultsfunction loadSettings() {const stored = store.read("settings");if (stored) {return JSON.parse(stored);}return {vibrate: true,brightness: 50};}const settings = loadSettings();console.log("Vibrate: " + settings.vibrate);// Modify and savesettings.brightness = 75;saveSettings(settings);store.close(); ``` ## Files (ECMA-419) For storing larger data, use the file system API via `device.files`: ``` const path = "example.json";// Write a fileconst jsonData = { name: "Alice", score: 1500 };const data = ArrayBuffer.fromString(JSON.stringify(jsonData));const save = device.files.openFile({ path, mode: "r+", size: data.byteLength });save.write(data, 0);save.close();// Read a fileconst load = device.files.openFile({ path });const loaded = load.read(load.status().size, 0);load.close();const parsed = JSON.parse(String.fromArrayBuffer(loaded));console.log(parsed.name);// "Alice"// Delete a filedevice.files.delete(path); ``` ### openFile() Options | Option | Description | | --- | --- | | `path` | File name (required) | | `mode` | `"r"` for read-only (default), `"r+"` for read-write | | `size` | File size in bytes (required when creating with `"r+"`) | ### File Methods | Method | Description | | --- | --- | | `write(buffer, offset)` | Write an ArrayBuffer at the given byte offset | | `read(count, offset)` | Read `count` bytes from the given byte offset | | `status()` | Returns an object with `size` property | | `close()` | Close the file | | `device.files.delete(path)` | Delete a file | Files are disposable, so instead of calling `close()` manually you can open them with a `using` declaration (SDK 4.33 and later) and they close automatically at the end of the scope: ``` using file = device.files.openFile({ path });const loaded = file.read(file.status().size, 0); ``` ## Choosing Between APIs | Feature | localStorage | Key-Value Storage | Files | | --- | --- | --- | --- | | API Style | Web standard | ECMA-419 | ECMA-419 | | Simplicity | Simpler | Moderate | More verbose | | Global access | Yes | Yes (`device.keyValue`) | Yes (`device.files`) | | Multiple stores | No | Yes (different paths) | Yes (different paths) | | Binary data | No (strings only) | Yes (buffer format) | Yes (ArrayBuffer) | | Random access | No | No | Yes (read/write at offset) | **Recommendation** : Use `localStorage` for most cases. Use Key-Value Storage when you need multiple isolated stores or binary key-value pairs. Use Files when you need random access or large binary data. ## Storage Limits Pebble has limited storage space. Keep stored data minimal: - Store only essential data - Clean up old or unused data - Avoid storing large objects - Consider compressing data if storing significant amounts ## Data Persistence Data stored with all three APIs persists across: - App restarts - Watch reboots - App updates (unless the app is uninstalled) Data is deleted when: - The app is uninstalled - The user performs a factory reset - You explicitly delete it with `removeItem()` or `delete()` ## Error Handling The storage APIs can fail if storage is full or corrupted: ``` try {localStorage.setItem("key", "value");} catch (e) {console.log("Failed to save data");} ``` For Key-Value Storage: ``` try {const store = device.keyValue.open({ path: "settings", format: "string" });store.write("key", "value");store.close();} catch (e) {console.log("Storage error: " + e.message);} ``` ## Complete Example: High Score Tracker ``` console.log("High Score Tracker");// Load existing high scoresfunction loadHighScores() {const stored = localStorage.getItem("highscores");if (stored) {return JSON.parse(stored);}return [];}// Save high scoresfunction saveHighScores(scores) {localStorage.setItem("highscores", JSON.stringify(scores));}// Add a new scorefunction addScore(name, score) {const scores = loadHighScores();scores.push({ name, score, date: Date.now() });// Keep only top 10scores.sort((a, b) =\> b.score - a.score);scores.splice(10);saveHighScores(scores);console.log("Added score: " + name + " - " + score);}// Display high scoresfunction showHighScores() {const scores = loadHighScores();console.log("=== HIGH SCORES ===");scores.forEach((entry, i) =\> {console.log((i + 1) + ". " + entry.name + ": " + entry.score);});}// Example usageaddScore("Alice", 1500);addScore("Bob", 2000);addScore("Charlie", 1750);showHighScores(); ``` ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes storage examples: - [`hellolocalstorage`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellolocalstorage) - persisting strings with the `localStorage` API - [`hellokeyvalue`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellokeyvalue) - key-value storage with string and binary data - [`hellofiles`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellofiles) - reading and writing JSON data using `device.files` --- # Networking Source: https://developer.repebble.com/guides/alloy/networking/ Alloy apps can communicate with the internet by proxying network requests through PebbleKit JS (PKJS) running on the phone. ## Understanding Watch vs Phone Code Alloy apps have two JavaScript environments: | Location | File Path | Runs On | Purpose | | --- | --- | --- | --- | | **embeddedjs** | `src/embeddedjs/main.js` | Watch | Your app UI and logic | | **PKJS** | `src/pkjs/index.js` | Phone | Network proxy, location, config | Code examples in this guide are labeled with **📱 PKJS** or **⌚ Watch** to indicate where they run. ## Setting Up the Network Proxy To use `fetch()` or `WebSocket` on the watch, install the `@moddable/pebbleproxy` package: ``` $ pebble package install @moddable/pebbleproxy ``` Then set up your `src/pkjs/index.js`: **📱 PKJS** (src/pkjs/index.js): ``` const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', moddableProxy.readyReceived);Pebble.addEventListener('appmessage', moddableProxy.appMessageReceived); ``` If your app also needs to handle its own events, call the proxy functions from your handlers: ``` const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', function(e) {moddableProxy.readyReceived(e);// Handle your own ready event here});Pebble.addEventListener('appmessage', function(e) {if (moddableProxy.appMessageReceived(e))return;// Handle your own app messages here}); ``` ## HTTP Requests with fetch() Once the proxy is set up, use `fetch()` in your watch code: **⌚ Watch** (src/embeddedjs/main.js): ``` async function fetchData() {const url = new URL("http://api.open-meteo.com/v1/forecast");url.search = new URLSearchParams({latitude: 37.7749,longitude: -122.4194,current: "temperature\_2m"});const response = await fetch(url);const data = await response.json();console.log("Temperature: " + data.current.temperature\_2m);} ``` ### Important Notes - **Wait for the proxy** - network requests only work after the proxy signals it is ready. Listen for `watch.addEventListener("connected", ...)` before calling `fetch()` or opening a `WebSocket` ### Response Methods | Method | Description | | --- | --- | | `response.json()` | Parse response as JSON | | `response.text()` | Get response as string | | `response.ok` | Boolean: true if status 200-299 | | `response.status` | HTTP status code | ## WebSockets WebSockets are also handled by the `@moddable/pebbleproxy` package - no additional proxy setup needed. **⌚ Watch** (src/embeddedjs/main.js): ``` const ws = new WebSocket("ws://websockets.chilkat.io/wsChilkatEcho.ashx");ws.binaryType = "arraybuffer";ws.addEventListener("open", event =\> {console.log("WebSocket connected");ws.send("Hello from Pebble!");ws.send(Uint8Array.of(0, 1, 2, 3, 4, 5));});ws.addEventListener("message", event =\> {let data = event.data;if (data instanceof ArrayBuffer) {console.log("Received binary data");data = new Uint8Array(data);} else {console.log("Received: " + data);if (data === "Goodbye") ws.close(1000, "Done");}});ws.addEventListener("close", event =\> {console.log("Closed: " + event.code + " " + event.reason);}); ``` ## Connection Status Check if the phone is connected from watch code: **⌚ Watch** (src/embeddedjs/main.js): ``` function logConnected() {console.log("App connected: " + watch.connected.app);console.log("PebbleKitJS connected: " + watch.connected.pebblekit);}watch.addEventListener('connected', logConnected);logConnected(); ``` Network requests only work once the proxy is ready. Wait until `watch.connected.pebblekit` is `true` before calling `fetch()` or opening a `WebSocket`. ## Best Practices 1. **Install `@moddable/pebbleproxy`** - required for `fetch()` and `WebSocket` 2. **Wait for the proxy** - network requests only work after the proxy is ready; listen for the `connected` event or check `watch.connected.pebblekit` 3. **Handle errors** - network requests can fail 4. **Minimize data** - request only what you need 5. **Cache responses** - use `localStorage` to reduce requests ## Advanced Networking For more control over HTTP and WebSocket connections, Alloy also provides low-level ECMA-419 networking APIs: - `HTTPClient` - streaming HTTP client with fine-grained header control - `WebSocketClient` - low-level WebSocket with callback-based API, provides streaming support These are also handled by `@moddable/pebbleproxy`. See the [Advanced Networking](https://developer.repebble.com/guides/alloy/advanced-networking.md) guide for details. ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes networking examples: - [`hellofetch`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellofetch) - HTTP requests using the `fetch()` API - [`hellowebsocket`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowebsocket) - WebSocket connections using the `WebSocket` API --- # App Messages Source: https://developer.repebble.com/guides/alloy/app-messages/ The Message API provides direct communication between watch code and PebbleKit JS (PKJS) on the phone. Unlike [networking](https://developer.repebble.com/guides/alloy/networking.md), app messages don't require the `@moddable/pebbleproxy` package - they use Pebble's built-in messaging system. Code examples in this guide are labeled with **📱 PKJS** or **⌚ Watch** to indicate where they run. ## Watch-Phone Messaging Use the `Message` class to send and receive structured data between the watch and PKJS: **⌚ Watch** (src/embeddedjs/main.js): ``` import Message from "pebble/message";const message = new Message({keys: ["COMMAND", "DATA", "RESULT"],onReadable() {const msg = this.read();msg.forEach((value, key) =\> {console.log(key + ": " + value);});},onWritable() {console.log("Ready to send messages");},onSuspend() {console.log("Messages suspended");}}); ``` The `keys` array lists the message key names used for communication. These must match the keys defined in `package.json`. The `onWritable` callback fires when the `Message` instance is ready to send messages. The `onSuspend` callback is the opposite - it fires when sending is temporarily unavailable (e.g., the PKJS connection is lost or another `Message` instance is sending). A new `Message` instance begins in the suspended state. ## Sending Messages from Watch **⌚ Watch** (src/embeddedjs/main.js): ``` message.write(new Map([["COMMAND", 1],["DATA", 42]])); ``` ## Message Keys in package.json Message keys must be defined in `package.json` as an array: ``` {"pebble": {"messageKeys": ["COMMAND","DATA","RESULT"]}} ``` ## Receiving Messages in PKJS **📱 PKJS** (src/pkjs/index.js): ``` Pebble.addEventListener('appmessage', function(e) {console.log("Received from watch: " + JSON.stringify(e.payload));// Send a response back to watchPebble.sendAppMessage({'RESULT': 123});}); ``` ## Combining with the Network Proxy If your app uses both app messages and the network proxy, set up the PKJS to handle both: **📱 PKJS** (src/pkjs/index.js): ``` const moddableProxy = require("@moddable/pebbleproxy");Pebble.addEventListener('ready', moddableProxy.readyReceived);Pebble.addEventListener('appmessage', function(e) {if (moddableProxy.appMessageReceived(e))return;// Handle your own app messages hereconsole.log("Received from watch: " + JSON.stringify(e.payload));}); ``` The proxy's `appMessageReceived()` returns `true` if the message was for the proxy (e.g., a `fetch()` request). If it returns `false`, the message is one of your own and you can handle it. ## Getting GPS Location For GPS location, use the `Location` sensor instead of app messages. See the [Sensors and Input](https://developer.repebble.com/guides/alloy/sensors-and-input.md) guide for details on the Location sensor, or the [watchface tutorial Part 4](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/part4.md) for a complete weather example. ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes app message examples: - [`hellomessage`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellomessage) - sending and receiving messages between watch and phone - [`helloconnected`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloconnected) - monitoring watch-phone connection status --- # Watchfaces Source: https://developer.repebble.com/guides/alloy/watchfaces/ Watchfaces are a special type of Pebble app that displays the current time. Alloy makes it easy to create custom watchfaces using either Poco or Piu. > **Note** : All code in this guide runs on the watch in `src/embeddedjs/main.js` unless otherwise noted. ## Configuration To create a watchface, set `watchface` to `true` in your `package.json`: ``` {"pebble": {"watchapp": {"watchface": true}}} ``` ## Time Events Subscribe to time change events using the `watch` global. The event callback receives a `Date` instance for the current time: ``` // Called every secondwatch.addEventListener("secondchange", e =\> {updateDisplay(e.date);});// Called every minutewatch.addEventListener("minutechange", e =\> {updateDisplay(e.date);}); ``` Use `secondchange` for watchfaces that show seconds, and `minutechange` for those that only show hours and minutes (saves battery). The callback is also invoked immediately when the event listener is installed, so there is no need to call your display function separately at startup. ## Basic Watchface Here's a simple digital watchface: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const font = new render.Font("Bitham-Black", 30);const black = render.makeColor(0, 0, 0);const white = render.makeColor(255, 255, 255);function draw(e) {render.begin();render.fillRectangle(white, 0, 0, render.width, render.height);// Get current time as HH:MM:SSconst time = e.date.toTimeString().slice(0, 8);// Center the textconst width = render.getTextWidth(time, font);const x = (render.width - width) / 2;const y = (render.height - font.height) / 2;render.drawText(time, font, black, x, y);render.end();}watch.addEventListener("secondchange", draw); ``` ## Getting Time Components Use the `Date` provided by the event to get individual time components: ``` function getTimeComponents(date) {return {hours: date.getHours(),// 0-23minutes: date.getMinutes(),// 0-59seconds: date.getSeconds(),// 0-59day: date.getDate(),// 1-31month: date.getMonth(),// 0-11year: date.getFullYear(),// e.g., 2025dayOfWeek: date.getDay()// 0-6 (Sunday=0)};} ``` ### Formatting Time ``` function formatTime(hours, minutes) {const h = String(hours).padStart(2, "0");const m = String(minutes).padStart(2, "0");return `${h}:${m}`;}function format12Hour(hours, minutes) {const period = hours \>= 12 ? "PM" : "AM";const h = hours % 12 || 12;const m = String(minutes).padStart(2, "0");return `${h}:${m} ${period}`;} ``` ## Watchface with Date ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const timeFont = new render.Font("Leco-Regular", 42);const dateFont = new render.Font("Gothic-Regular", 18);const black = render.makeColor(0, 0, 0);const white = render.makeColor(255, 255, 255);const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun","Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];function draw(e) {const now = e.date;render.begin();render.fillRectangle(white, 0, 0, render.width, render.height);// Time (HH:MM)const hours = String(now.getHours()).padStart(2, "0");const minutes = String(now.getMinutes()).padStart(2, "0");const timeStr = `${hours}:${minutes}`;let width = render.getTextWidth(timeStr, timeFont);render.drawText(timeStr, timeFont, black,(render.width - width) / 2,(render.height - timeFont.height) / 2 - 15);// Date (e.g., "Mon, Jan 15")const dayName = DAYS[now.getDay()];const monthName = MONTHS[now.getMonth()];const dateStr = `${dayName}, ${monthName} ${now.getDate()}`;width = render.getTextWidth(dateStr, dateFont);render.drawText(dateStr, dateFont, black,(render.width - width) / 2,(render.height - dateFont.height) / 2 + 30);render.end();}watch.addEventListener("minutechange", draw); ``` ## Analog Watchface Concepts For analog watchfaces, calculate hand positions based on time: ``` function getHandAngles(now) {const hours = now.getHours() % 12;const minutes = now.getMinutes();const seconds = now.getSeconds();// Angles in degrees (0 = 12 o'clock)const secondAngle = seconds \* 6;// 360/60 = 6 degrees per secondconst minuteAngle = minutes \* 6 + seconds \* 0.1;const hourAngle = hours \* 30 + minutes \* 0.5; // 360/12 = 30 degrees per hourreturn { hourAngle, minuteAngle, secondAngle };} ``` ### Drawing Clock Hands ``` function drawHand(cx, cy, angle, length, color, thickness) {// Convert angle to radians (0 degrees = up)const radians = (angle - 90) \* Math.PI / 180;const endX = cx + Math.cos(radians) \* length;const endY = cy + Math.sin(radians) \* length;render.drawLine(cx, cy, endX, endY, color, thickness);}function drawAnalogClock(e) {const cx = render.width / 2;const cy = render.height / 2;const { hourAngle, minuteAngle, secondAngle } = getHandAngles(e.date);render.begin();render.fillRectangle(white, 0, 0, render.width, render.height);// Hour handdrawHand(cx, cy, hourAngle, 35, black, 4);// Minute handdrawHand(cx, cy, minuteAngle, 55, black, 3);// Second handdrawHand(cx, cy, secondAngle, 60, red, 1);// Center dotrender.drawCircle(black, cx, cy, 4, 0, 360);render.end();} ``` ## Using Piu for Watchfaces You can also build watchfaces with the Piu framework: ``` import {} from "piu/MC";const backgroundSkin = new Skin({ fill: "white" });const timeStyle = new Style({ font: "42px Bitham-Black", color: "black" });class ClockBehavior extends Behavior {onDisplaying(label) {this.updateTime(label);}updateTime(label, date) {const h = String(date.getHours()).padStart(2, "0");const m = String(date.getMinutes()).padStart(2, "0");label.string = `${h}:${m}`;}}const WatchfaceApp = Application.template($ =\> ({skin: backgroundSkin,contents: [Label($, {anchor: "TIME",style: timeStyle,Behavior: ClockBehavior})],Behavior: class extends Behavior {onCreate(app, data) {this.data = data;watch.addEventListener("minutechange", e =\> {this.data.TIME.delegate("updateTime", e.date);});}}}));export default new WatchfaceApp({}, {}); ``` ## Battery and Connection Status Good watchfaces show battery and connection status: ``` import Battery from "embedded:sensor/Battery";let batteryPercent = 100;let isConnected = false;// Battery monitoringconst battery = new Battery({onSample() {batteryPercent = this.sample().percent;updateDisplay();}});batteryPercent = battery.sample().percent;// Connection monitoringfunction checkConnection() {isConnected = watch.connected.app;console.log("Connected: " + isConnected);updateDisplay();}watch.addEventListener('connected', checkConnection);checkConnection();function drawStatusBar() {// Battery indicatorconst batteryText = batteryPercent + "%";render.drawText(batteryText, smallFont, gray, 10, 5);// Connection indicator (show X when disconnected)if (!isConnected) {render.drawText("X", smallFont, gray, render.width - 20, 5);}} ``` ## Performance Tips 1. **Use minutechange when possible** : Updating every minute instead of every second significantly improves battery life. 2. **Minimize redraw area** : Use `render.begin(x, y, w, h)` to only update changed portions of the screen. 3. **Precompute values** : Calculate font sizes, positions, and colors once at startup. 4. **Avoid complex calculations in draw** : Do math outside the render begin/end block when possible. 5. **Use the provided Date** : Use the `Date` instance from the event instead of creating your own. ## Complete Example: Minimal Watchface ``` import Poco from "commodetto/Poco";import Battery from "embedded:sensor/Battery";const render = new Poco(screen);const timeFont = new render.Font("Leco-Regular", 42);const dateFont = new render.Font("Gothic-Regular", 18);const black = render.makeColor(0, 0, 0);const white = render.makeColor(255, 255, 255);const gray = render.makeColor(128, 128, 128);const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"];let batteryPercent = 100;const battery = new Battery({onSample() {batteryPercent = this.sample().percent;}});batteryPercent = battery.sample().percent;function draw(e) {const now = e.date;render.begin();render.fillRectangle(black, 0, 0, render.width, render.height);// Timeconst h = String(now.getHours()).padStart(2, "0");const m = String(now.getMinutes()).padStart(2, "0");const timeStr = `${h}:${m}`;let width = render.getTextWidth(timeStr, timeFont);render.drawText(timeStr, timeFont, white,(render.width - width) / 2,(render.height / 2) - timeFont.height);// Day of weekconst dayStr = DAYS[now.getDay()];width = render.getTextWidth(dayStr, dateFont);render.drawText(dayStr, dateFont, gray,(render.width - width) / 2,(render.height / 2) + 10);// Batteryconst batteryStr = `${batteryPercent}%`;width = render.getTextWidth(batteryStr, dateFont);render.drawText(batteryStr, dateFont, gray,(render.width - width) / 2,render.height - dateFont.height - 10);render.end();}watch.addEventListener("minutechange", draw); ``` ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes watchface examples: - [`hellowatchface`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowatchface) - simple digital watchface using Poco - [`piu/watchfaces/cupertino`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/cupertino) - classic macOS cursor clock - [`piu/watchfaces/london`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/london) - Big Ben-inspired analog watchface - [`piu/watchfaces/helsinki`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/helsinki) - Nokia style digital watchface - [`piu/watchfaces/redmond`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/redmond) - classic Windows clock - [`piu/watchfaces/zurich`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/zurich) - Swiss train station clock --- # Animations Source: https://developer.repebble.com/guides/alloy/animations/ Alloy provides powerful animation capabilities through the Timeline class and built-in easing functions. These work with both Piu UI components and Poco graphics. > **Note** : All code in this guide runs on the watch in `src/embeddedjs/main.js`. ## Timeline Basics The Timeline class creates smooth, coordinated animations: ``` import Timeline from "piu/Timeline";class AnimatedBehavior extends Behavior {onDisplaying(content) {const timeline = this.timeline = new Timeline();// Animate 'y' property from current to 100 over 750mstimeline.to(content, { y: 100 }, 750, Math.quadEaseOut, 0);// Setup playbackcontent.duration = timeline.duration;timeline.seekTo(0);content.time = 0;content.start();}onTimeChanged(content) {this.timeline.seekTo(content.time);}onFinished(content) {console.log("Animation complete!");}} ``` ### Timeline Methods | Method | Description | | --- | --- | | `to(target, properties, duration, easing, delay)` | Animate properties to new values | | `from(target, properties, duration, easing, delay)` | Animate properties from values | | `on(target, properties, duration, easing, delay)` | Keyframe animation | | `seekTo(time)` | Jump to specific time in animation | ### Timeline Properties | Property | Description | | --- | --- | | `duration` | Total duration of all animations | ## Easing Functions Easing functions control the rate of change during animations. All easing functions are available on the `Math` object: ### Available Easing Functions The [`hellopiu-timeline`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-timeline) example demonstrates most of these easing equations in action. | Ease In | Ease Out | Description | | --- | --- | --- | | `Math.backEaseIn` | `Math.backEaseOut` | Overshoots then returns | | `Math.bounceEaseIn` | `Math.bounceEaseOut` | Bouncing effect | | `Math.circularEaseIn` | `Math.circularEaseOut` | Circular curve | | `Math.cubicEaseIn` | `Math.cubicEaseOut` | Cubic curve (smooth) | | `Math.elasticEaseIn` | `Math.elasticEaseOut` | Spring/elastic effect | | `Math.exponentialEaseIn` | `Math.exponentialEaseOut` | Exponential curve | | `Math.quadEaseIn` | `Math.quadEaseOut` | Quadratic curve | | `Math.quartEaseIn` | `Math.quartEaseOut` | Quartic curve | | `Math.quintEaseIn` | `Math.quintEaseOut` | Quintic curve | | `Math.sineEaseIn` | `Math.sineEaseOut` | Sinusoidal curve | ### Using Easing Functions ``` // Linear (no easing) - define your ownfunction linearEase(fraction) {return fraction;}// Use built-in easingtimeline.to(content, { y: 200 }, 500, Math.bounceEaseOut, 0);// Ease in then ease outtimeline.to(content, { x: 100 }, 300, Math.quadEaseIn, 0);timeline.to(content, { x: 200 }, 300, Math.quadEaseOut, 300); ``` ### Easing Function Characteristics - **EaseIn** : Starts slow, ends fast - **EaseOut** : Starts fast, ends slow - **Bounce** : Creates a bouncing effect at the end - **Elastic** : Creates a spring-like overshoot - **Back** : Pulls back before moving forward ## Animating in Poco For Poco graphics, use easing functions with `setInterval`: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const gray = render.makeColor(128, 128, 128);const blue = render.makeColor(0, 100, 255);const start = Date.now();const duration = 1500;setInterval(() =\> {// Calculate animation progress (0 to 1)const elapsed = (Date.now() - start) % duration;const progress = elapsed / duration;// Apply easing functionconst eased = Math.elasticEaseOut(progress);// Animate size based on eased valueconst size = 20 + (60 \* eased);const x = (render.width - size) / 2;const y = (render.height - size) / 2;render.begin();render.fillRectangle(gray, 0, 0, render.width, render.height);render.fillRectangle(blue, x, y, size, size);render.end();}, 50);// ~20fps ``` ## Scaling PDC Images Animate PDC image scaling with easing: ``` import Poco from "commodetto/Poco";const render = new Poco(screen);const gray = render.makeColor(128, 128, 128);const dci = new Poco.PebbleDrawCommandImage(2);console.log("PDC size: " + dci.width + " x " + dci.height);const start = Date.now();setInterval(() =\> {// Calculate scale with elastic easingconst progress = ((Date.now() - start) % 1500) / 1500;const scale = Math.elasticEaseOut(progress);// Calculate centered position for scaled imageconst cx = (render.width - dci.width \* scale) / 2;const cy = (render.height - dci.height \* scale) / 2;render.begin();render.fillRectangle(gray, 0, 0, render.width, render.height);render.drawDCI(dci.clone().scale(scale), cx, cy);render.end();}, 50); ``` ## Complete Piu Animation Example Here's a complete example showing Timeline animations with multiple easing functions: ``` import {} from "piu/MC";import Timeline from "piu/Timeline";const WHITE = "white";const BLACK = "black";const GRAY = "gray";const backgroundSkin = new Skin({ fill: BLACK });const headerSkin = new Skin({ fill: WHITE });const boxSkin = new Skin({ fill: GRAY });const borderSkin = new Skin({fill: "transparent",stroke: WHITE,borders: { left: 2, right: 2, top: 2, bottom: 2 }});const headerStyle = new Style({font: "bold 18px Gothic",color: BLACK});// List of easing functions to demonstrateconst easingFunctions = [{ name: "quadEase", out: Math.quadEaseOut, in: Math.quadEaseIn },{ name: "bounceEase", out: Math.bounceEaseOut, in: Math.bounceEaseIn },{ name: "elasticEase", out: Math.elasticEaseOut, in: Math.elasticEaseIn },{ name: "backEase", out: Math.backEaseOut, in: Math.backEaseIn },];class AnimationBehavior extends Behavior {onCreate(container) {this.index = 0;}onDisplaying(container) {this.startAnimation(container);}startAnimation(container) {const easing = easingFunctions[this.index];const box = container.first;const bottom = container.height - box.height - 4;// Animate downconst timeline = this.timeline = new Timeline();timeline.to(box, { y: bottom }, 750, easing.out, 0);timeline.to(box, { y: 4 }, 750, easing.in, 250);container.duration = timeline.duration + 500;timeline.seekTo(0);container.time = 0;container.start();}onTimeChanged(container) {this.timeline.seekTo(container.time);}onFinished(container) {// Move to next easing functionthis.index = (this.index + 1) % easingFunctions.length;this.startAnimation(container);}}const AnimationApp = Application.template($ =\> ({skin: backgroundSkin,contents: [Column($, {top: 0, bottom: 0, left: 0, right: 0,contents: [Label($, {top: 0, height: 30, left: 0, right: 0,skin: headerSkin,style: headerStyle,string: "Animation Demo"}),Container($, {top: 10, bottom: 10, left: 10, right: 10,skin: borderSkin,contents: [Content($, {top: 4, height: 25, width: 25,skin: boxSkin})],Behavior: AnimationBehavior})]})]}));export default new AnimationApp({}, {}); ``` ## Animation Best Practices 1. **Choose appropriate easing** : Use `quadEaseOut` for UI transitions, `bounceEaseOut` for playful effects, `elasticEaseOut` for attention-grabbing animations. 2. **Keep animations short** : 200-500ms for UI transitions, up to 1000ms for decorative animations. 3. **Use appropriate frame rates** : 30fps (33ms interval) is usually sufficient. 60fps (17ms interval) for smoother animations at higher battery cost. Note that these are requests - if Pebble OS can't keep up, the actual frame rate will be lower. Smoothest results tend to come from requesting a frame rate that can be consistently achieved. 4. **Clean up animations** : Stop intervals and timelines when no longer needed. 5. **Consider battery life** : Frequent screen updates drain battery. Use animations sparingly on watchfaces. ## Combining Animations Chain multiple animations using Timeline delays: ``` const timeline = new Timeline();// First animation: move righttimeline.to(content, { x: 100 }, 300, Math.quadEaseOut, 0);// Second animation: move down (starts after 300ms)timeline.to(content, { y: 100 }, 300, Math.quadEaseOut, 300);// Third animation: fade (starts after 600ms)timeline.to(content, { state: 1 }, 200, Math.quadEaseOut, 600); ``` ## Looping Animations Create looping animations by restarting in `onFinished`: ``` class LoopingBehavior extends Behavior {onDisplaying(content) {this.setupAnimation(content);}setupAnimation(content) {const timeline = this.timeline = new Timeline();timeline.to(content, { y: 100 }, 500, Math.sineEaseOut, 0);timeline.to(content, { y: 0 }, 500, Math.sineEaseIn, 500);content.duration = timeline.duration;timeline.seekTo(0);content.time = 0;content.start();}onTimeChanged(content) {this.timeline.seekTo(content.time);}onFinished(content) {// Restart the animationthis.setupAnimation(content);}} ``` ## Examples The [Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository includes an animation example: - [`hellopiu-timeline`](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-timeline) - demonstrates various easing equations with Timeline animation --- # Alloy Source: https://developer.repebble.com/guides/alloy/ Alloy is a JavaScript framework for building Pebble apps, based on the [Moddable SDK](https://www.moddable.com/). Alloy provides developers a mix of standard Web APIs, standard Embedded JavaScript APIs from ECMA-419, and dedicated Pebble OS APIs. It allows you to write apps using modern JavaScript (ES2025, ES6++) with powerful UI frameworks and access to Pebble hardware features. > **Platform Support** : Alloy currently supports Emery (Pebble Time 2) and Gabbro (Pebble Round 2). See the platform support table in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md#platform-support). ## Key Features - **Modern JavaScript** : Write apps using ES modules, classes, async/await, and other modern JavaScript features - **Two UI Frameworks** : Choose between Piu (declarative, component-based) or Poco (procedural, low-level graphics) - **Hardware Access** : Full access to accelerometer, battery, buttons, compass, and other Pebble sensors - **Network Communication** : Built-in support for HTTP requests, WebSockets, and watch-phone messaging - **Persistent Storage** : Simple APIs for storing data locally using `localStorage`, key-value storage, or files ## Getting Started To create a new Alloy project: ``` $ pebble new-project --alloy my-app ``` This creates a new project with the standard Alloy structure: ``` my-app/ src/ embeddedjs/ main.js # Watch code (runs on Pebble) pkjs/ index.js # Phone code (for networking, location) c/ mdbl.c # C code as entry point for embeddedjs (usually no need to modify) resources/ # App resources (images, fonts) package.json # App manifest ``` Alloy apps have two JavaScript environments: **embeddedjs** runs on the watch, while **pkjs** runs on the connected phone for network and location services. ## Hello World The simplest Alloy app: ``` console.log("Hello, Pebble!"); ``` ## Rocky.js Rocky.js is no longer part of the firmware or the SDK. Alloy replaces it. ## Guides - [**Getting Started with Alloy**](https://developer.repebble.com/guides/alloy/getting-started.md) - Learn how to create your first Alloy app for Pebble. - [**Piu UI Framework**](https://developer.repebble.com/guides/alloy/piu-guide.md) - Build declarative user interfaces with the Piu framework. - [**Poco Graphics**](https://developer.repebble.com/guides/alloy/poco-guide.md) - Low-level graphics rendering with the Poco framework. - [**Sensors and Input**](https://developer.repebble.com/guides/alloy/sensors-and-input.md) - Access Pebble sensors and handle button input in Alloy apps. - [**Storage**](https://developer.repebble.com/guides/alloy/storage.md) - Persist data between app launches using localStorage, key-value storage, or files. - [**Networking**](https://developer.repebble.com/guides/alloy/networking.md) - Make HTTP requests and use WebSockets via the phone proxy. - [**App Messages**](https://developer.repebble.com/guides/alloy/app-messages.md) - Send and receive messages between the watch and the phone. - [**Watchfaces**](https://developer.repebble.com/guides/alloy/watchfaces.md) - Build custom watchfaces with Alloy. - [**Animations**](https://developer.repebble.com/guides/alloy/animations.md) - Create smooth animations using Timeline and easing functions. - [**Port (Custom Drawing)**](https://developer.repebble.com/guides/alloy/port-drawing.md) - Combine Piu's declarative UI with custom drawing using Port. - [**Advanced Networking**](https://developer.repebble.com/guides/alloy/advanced-networking.md) - Low-level HTTP and WebSocket clients for advanced networking needs. - [**Native Functions (FFI)**](https://developer.repebble.com/guides/alloy/ffi.md) - Call native C functions directly from JavaScript in your Alloy app using the Foreign Function Interface. - [**Dictation**](https://developer.repebble.com/guides/alloy/dictation.md) - Capture speech as text in Alloy apps using the dictation API. - [**Wakeups**](https://developer.repebble.com/guides/alloy/wakeups.md) - Schedule your Alloy app to launch at a future time using wakeups. - [**Vibration**](https://developer.repebble.com/guides/alloy/vibration.md) - Provide haptic feedback in Alloy apps using the vibration motor. - [**Device Info and App Events**](https://developer.repebble.com/guides/alloy/device-info.md) - Query watch and screen properties, respond to app lifecycle events, and control the backlight in Alloy apps. - [**Health**](https://developer.repebble.com/guides/alloy/health.md) - Read steps, sleep, activities and heart rate data from Pebble Health in Alloy apps. - [**Alloy Reference**](https://developer.repebble.com/guides/alloy/reference.md) - Where the Alloy APIs are documented, and the details that are not obvious from the other guides: font names, resource IDs, modules, memory and TypeScript. ## Example Apps The [Moddable Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repository contains a collection of examples covering all aspects of Alloy development. Here's a categorized overview: ### Fundamentals | Example | Description | | --- | --- | | [hellopebble](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopebble) | "Hello, world" - the simplest starting point | | [hellotimer](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellotimer) | Using `setTimeout` | | [hellomodule](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellomodule) | Loading multiple modules | | [hellotypescript](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellotypescript) | TypeScript with the Pebble `Button` class | ### Storage | Example | Description | | --- | --- | | [hellokeyvalue](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellokeyvalue) | ECMA-419 Key-Value Storage for persistent data | | [hellolocalstorage](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellolocalstorage) | Web standard `localStorage` for persisting strings | | [hellofiles](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellofiles) | File system storage | ### Sensors & Input | Example | Description | | --- | --- | | [helloaccelerometer](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloaccelerometer) | Subscribing to accelerometer readings | | [hellobattery](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellobattery) | Battery and charging status | | [hellolocation](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellolocation) | GPS location via phone | | [hellobutton](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellobutton) | Pebble button events | | [hellopoco-drag](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-drag) | Touch events with the ECMA-419 touch driver | ### Piu UI Framework | Example | Description | | --- | --- | | [hellopiu-text](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-text) | Dynamic text layout with different fonts | | [hellopiu-pebbletext](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-pebbletext) | Text using Pebble built-in fonts | | [hellopiu-balls](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-balls) | Classic bouncing balls demo | | [hellopiu-coloredsquares](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-coloredsquares) | Drawing colored squares | | [hellopiu-gbitmap](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-gbitmap) | Pebble GBitmap PNG images as Piu textures | | [hellopiu-jsicon](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-jsicon) | Moddable SDK bitmaps as Piu textures | | [hellopiu-port](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-port) | Animated graph using Piu Port | | [hellopiu-timeline](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-timeline) | Easing equations with Timeline animation | ### Piu Watchfaces | Example | Description | | --- | --- | | [cupertino](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/cupertino) | Classic macOS watch cursor as a watchface | | [london](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/london) | Big Ben (color watches only) | | [helsinki](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/helsinki) | Minimal design with per-model assets | | [redmond](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/redmond) | Classic Windows clock | | [zurich](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/zurich) | Iconic Swiss railway clock | ### Poco Renderer | Example | Description | | --- | --- | | [hellopoco-text](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-text) | Text rendering with Moddable SDK fonts | | [hellopoco-pebbletext](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pebbletext) | Text rendering with Pebble built-in fonts | | [hellopoco-gbitmap](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-gbitmap) | Rendering GBitmap resources | | [hellopoco-pebblegraphics](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pebblegraphics) | Lines, round rectangles, and circles | | [hellopoco-qrcode](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-qrcode) | Dynamic QR code generation | | [hellopoco-pdc](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc) | Rendering PDC (SVG) images | | [hellopoco-pdc-rotate](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc-rotate) | Spinning a PDC image | | [hellopoco-pdc-scale](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc-scale) | Animated PDC scaling with easing | | [hellopoco-pdc-sequence](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc-sequence) | PDC image sequence animation | | [hellowatchface](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowatchface) | Simple watchface app | ### Communication | Example | Description | | --- | --- | | [hellomessage](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellomessage) | Watch-phone messaging via app\_message | | [helloconnected](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloconnected) | Phone connection status notifications | | [hellofetch](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellofetch) | HTTP requests using the `fetch()` API | | [hellohttpclient](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellohttpclient) | HTTP requests using ECMA-419 HTTP Client | | [hellowebsocket](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowebsocket) | WebSocket using the Web standard API | | [hellowebsocketclient](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowebsocketclient) | WebSocket using ECMA-419 WebSocket Client | ### Sensor Visualizations (Piu) | Example | Description | | --- | --- | | [compass](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/compass) | Compass visualization (Emery only) | | [gravity](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/gravity) | Accelerometer visualization (Emery only) | | [tic-tac-toe](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/apps/tic-tac-toe) | Touch game using Piu Behavior touch events | ### Device, Events & Feedback | Example | Description | | --- | --- | | [helloinfo](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloinfo) | Watch, screen, and device information | | [helloappevents](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloappevents) | App focus and display resize events | | [hellowakeup](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellowakeup) | Scheduling the app to launch later with wakeups | | [hellodictation](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellodictation) | Capturing speech as text with dictation | | [hellovibes](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellovibes) | Haptic feedback with the vibration motor | | [hellolight](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellolight) | Controlling the backlight | ### Native Code | Example | Description | | --- | --- | | [helloffi](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/helloffi) | Calling native C functions from JavaScript (FFI) | ## Additional Resources - [Moddable SDK Documentation](https://www.moddable.com/documentation/readme) - Comprehensive documentation for Piu, Poco, and other modules --- # Advanced Communication Source: https://developer.repebble.com/guides/communication/advanced-communication/ Many types of connected Pebble watchapps and watchfaces perform common tasks such as the ones discussed here. Following these best practices can increase the quality of the implementation of each one, and avoid common bugs. ## Waiting for PebbleKit JS Any app that wishes to send data from the watch to the phone via [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) **must** wait until the JavaScript `ready` event has occured, indicating that the phone has loaded the JavaScript component of the launching app. If this JavaScript code implements the `appmessage` event listsner, it is ready to receive data. > An watchapp that only _receives_ data from PebbleKit JS does not have to wait for the `ready` event. In addition, Android companion apps do not have to wait for such an event thanks to the `Intent` system. iOS companion apps must wait for `-watchDidConnect:`. A simple method is to define a key in `package.json` that will be interpreted by the watchapp to mean that the JS environment is ready for exchange data: ``` "messageKeys": ["JSReady"] ``` The watchapp should implement a variable that describes if the `ready` event has occured. An example is shown below: ``` static bool s\_js\_ready; ``` This can be exported in a header file for other parts of the app to check. Any parts of the app that are waiting should call this as part of a [retry](#timeouts-and-retries) mechanism. ``` bool comm\_is\_js\_ready() {return s\_js\_ready;} ``` The state of this variable will be `false` until set to `true` when the `ready` event causes the key to be transmitted: ``` Pebble.addEventListener('ready', function() {console.log('PebbleKit JS ready.');// Update s\_js\_ready on watchPebble.sendAppMessage({'JSReady': 1});}); ``` This key should be interpreted in the app's [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") implementation: ``` static void inbox\_received\_handler(DictionaryIterator \*iter, void \*context) {Tuple \*ready\_tuple = dict\_find(iter, MESSAGE\_KEY\_JSReady);if(ready\_tuple) {// PebbleKit JS is ready! Safe to send messagess\_js\_ready = true;}} ``` ## Timeouts and Retries Due to the wireless and stateful nature of the Bluetooth connection, some messages sent between the watch and phone may fail. A tried-and-tested method for dealing with these failures is to implement a 'timeout and retry' mechanism. Under such a scheme: - A message is sent and a timer started. - If the message is sent successfully (and optionally a reply received), the timer is cancelled. - If the timer elapses before the message can be sent successfully, the message is reattempted. Depending on the nature of the failure, a suitable retry interval (such as a few seconds) is used to avoid saturating the connection. The interval chosen before a timeout occurs and the message is resent may vary depending on the circumstances. The first failure should be reattempted fairly quickly (one second), with the interval increasing as successive failures occurs. If the connection is not available the timer interval should be [even longer](https://en.wikipedia.org/wiki/Exponential_backoff), or wait until the connection is restored. ### Using a Timeout Timer The example below shows the sending of a message and scheduling a timeout timer. The first step is to declare a handle for the timeout timer: ``` static AppTimer \*s\_timeout\_timer; ``` When the message is sent, the timer should be scheduled: ``` static void send\_with\_timeout(int key, int value) {// Construct and send the messageDitionaryIterator \*iter;if(app\_message\_outbox\_begin(&iter) == APP\_MSG\_OK) {dict\_write\_int(iter, key, &value, sizeof(int), true);app\_message\_outbox\_send();}// Schedule the timeout timerconst int interval\_ms = 1000;s\_timout\_timer = app\_timer\_register(interval\_ms, timout\_timer\_handler, NULL);} ``` If the [`AppMessageOutboxSent`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent "AppMessageOutboxSent") is called, the message was a success, and the timer should be cancelled: ``` static void outbox\_sent\_handler(DictionaryIterator \*iter, void \*context) {// Successful message, the timeout is not needed anymore for this messageapp\_timer\_cancel(s\_timout\_timer);} ``` ### Retry a Failed Message However, if the timeout timer elapses before the message's success can be determined or an expected reply is not received, the callback to `timout_timer_handler()` should be used to inform the user of the failure, and schedule another attempt and retry the message: ``` static void timout\_timer\_handler(void \*context) {// The timer elapsed because no success was reportedtext\_layer\_set\_text(s\_status\_layer, "Failed. Retrying...");// Retry the messagesend\_with\_timeout(some\_key, some\_value);} ``` Alternatively, if the [`AppMessageOutboxFailed`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxFailed "AppMessageOutboxFailed") is called the message failed to send, sometimes immediately. The timeout timer should be cancelled and the message reattempted after an additional delay (the 'retry interval') to avoid saturating the channel: ``` static void outbox\_failed\_handler(DictionaryIterator \*iter,AppMessageResult reason, void \*context) {// Message failed before timer elapsed, reschedule for laterif(s\_timout\_timer) {app\_timer\_cancel(s\_timout\_timer);}// Inform the user of the failuretext\_layer\_set\_text(s\_status\_layer, "Failed. Retrying...");// Use the timeout handler to perform the same action - resend the messageconst int retry\_interval\_ms = 500;app\_timer\_register(retry\_interval\_ms, timout\_timer\_handler, NULL);} ``` > Note: All eventualities where a message fails must invoke a resend of the message, or the purpose of an automated 'timeout and retry' mechanism is defeated. However, the number of attempts made and the interval between them is for the developer to decide. ## Sending Lists Until SDK 3.8, the size of [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") buffers did not facilitate sending large amounts of data in one message. With the current buffer sizes of up to 8k for each an outbox the need for efficient transmission of multiple sequential items of data is lessened, but the technique is still important. For instance, to transmit sensor data as fast as possible requires careful scheduling of successive messages. Because there is no guarantee of how long a message will take to transmit, simply using timers to schedule multiple messages after one another is not reliable. A much better method is to make good use of the callbacks provided by the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") API. ### Sending a List to the Phone For instance, the [`AppMessageOutboxSent`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent "AppMessageOutboxSent") callback can be used to safely schedule the next message to the phone, since the previous one has been acknowledged by the other side at that time. Here is an example array of items: ``` static int s\_data[] = { 2, 4, 8, 16, 32, 64 };#define NUM\_ITEMS sizeof(s\_data); ``` A variable can be used to keep track of the current list item index that should be transmitted next: ``` static int s\_index = 0; ``` When a message has been sent, this index is used to construct the next message: > Note: A useful key scheme is to use the item's array index as the key. For PebbleKit JS that number of keys will have to be declared in `package.json`, like so: `someArray[6]` ``` static void outbox\_sent\_handler(DictionaryIterator \*iter, void \*context) {// Increment the indexs\_index++;if(s\_index \< NUM\_ITEMS) {// Send the next itemDictionaryIterator \*iter;if(app\_message\_outbox\_begin(&iter) == APP\_MSG\_OK) {dict\_write\_int(iter, MESSAGE\_KEY\_someArray + s\_index, &s\_data[s\_index], sizeof(int), true);app\_message\_outbox\_send();}} else {// We have reached the end of the sequenceAPP\_LOG(APP\_LOG\_LEVEL\_INFO, "All transmission complete!");}} ``` This results in a callback loop that repeats until the last data item has been transmitted, and the index becomes equal to the total number of items. This technique can be combined with a timeout and retry mechanism to reattempt a particular item if transmission fails. This is a good way to avoid gaps in the received data items. On the phone side, the data items are received in the same order. An analogous `index` variable is used to keep track of which item has been received. This process will look similar to the example shown below: ``` var NUM\_ITEMS = 6;var keys = require('message\_keys');var data = [];var index = 0;Pebble.addEventListener('appmessage', function(e) {// Store this data itemdata[index] = e.payload[keys.someArray + index];// Increment index for next messageindex++;if(index == NUM\_ITEMS) {console.log('Received all data items!');}}); ``` ### Sending a List to Pebble Conversely, the `success` callback of `Pebble.sendAppMessage()` in PebbleKit JS is the equivalent safe time to send the next message to the watch. An example implementation that achieves this is shown below. After the message is sent with `Pebble.sendAppMessage()`, the `success` callback calls the `sendNextItem()` function repeatedly until the index is larger than that of the last list item to be sent, and transmission will be complete. Again, an index variable is maintained to keep track of which item is being transmitted: ``` var keys = require('message\_keys');function sendNextItem(items, index) {// Build messagevar key = keys.someArray + index;var dict = {};dict[key] = items[index];// Send the messagePebble.sendAppMessage(dict, function() {// Use success callback to increment indexindex++;if(index \< items.length) {// Send next itemsendNextItem(items, index);} else {console.log('Last item sent!');}}, function() {console.log('Item transmission failed at index: ' + index);});}function sendList(items) {var index = 0;sendNextItem(items, index);}function onDownloadComplete(responseText) {// Some web response containing a JSON object arrayvar json = JSON.parse(responseText);// Begin transmission loopsendList(json.items);} ``` On the watchapp side, the items are received in the same order in the [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") handler: ``` #define NUM\_ITEMS 6static int s\_index;static int s\_data[NUM\_ITEMS]; ``` ``` static void inbox\_received\_handler(DictionaryIterator \*iter, void \*context) {Tuple \*data\_t = dict\_find(iter, MESSAGE\_KEY\_someArray + s\_index);if(data\_t) {// Store this items\_data[index] = (int)data\_t-\>value-\>int32;// Increment index for next items\_index++;}if(s\_index == NUM\_ITEMS) {// We have reached the end of the sequenceAPP\_LOG(APP\_LOG\_LEVEL\_INFO, "All transmission complete!");}} ``` This sequence of events is demonstrated for PebbleKit JS, but the same technique can be applied exactly to either and Android or iOS companion app wishing to transmit many data items to Pebble. Get the complete source code for this example from the [`list-items-example`](https://github.com/pebble-examples/list-items-example) repository on GitHub. ## Sending Image Data A common task developers want to accomplish is display a dynamically loaded image resource (for example, showing an MMS photo or a news item thumbnail pulled from a webservice). Because some images could be larger than the largest buffer size available to the app, the techniques shown above for sending lists also prove useful here, as the image is essentially a list of color byte values. ### Image Data Format There are two methods available for displaying image data downloaded from the web: 1. Download a `png` image, transmit the compressed data, and decompress using [`gbitmap_create_from_png_data()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_from_png_data "gbitmap\_create\_from\_png\_data"). This involves sending less data, but can be prone to failure depending on the exact format of the image. The image must be in a compatible palette (1, 2, 4, or 8-bit) and small enough such that there is enough memory for a compessed copy, an uncompressed copy, and ~2k overhead when it is being processed. 2. Download a `png` image, decompress in the cloud or in PebbleKit JS into an array of image pixel bytes, transmit the pixel data into a blank [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap")'s `data` member. Each byte must be in the compatible Pebble color format (2 bits per ARGB). This process can be simplified by pre-formatting the image to be dowloaded, as resizing or palette-reduction is difficult to do locally. ### Sending Compressed PNG Data As the fastest and least complex of the two methods described above, an example of how to display a compressed PNG image will be discussed here. The image that will be displayed is [the HTML 5 logo](https://www.w3.org/html/logo/): ![The HTML5 logo.](https://developer.repebble.com/assets/images/guides/pebble-apps/communications/html5-logo-small.png) > Note: The above image has been resized and palettized for compatibility. To download this image in PebbleKit JS, use an `XmlHttpRequest` object. It is important to specify the `responseType` as 'arraybuffer' to obtain the image data in the correct format: ``` function downloadImage() {var url = 'https://developer.repebble.com/assets/images/guides/pebble-apps/communications/html5-logo-small.png';var request = new XMLHttpRequest();request.onload = function() {processImage(this.response);};request.responseType = "arraybuffer";request.open("GET", url);request.send();} ``` When the response has been received, `processImage()` will be called. The received data must be converted into an array of unsigned bytes, which is achieved through the use of a `Uint8Array`. This process is shown below (see the [`png-download-example`](https://github.com/pebble-examples/png-download-example) repository for the full example): ``` function processImage(responseData) {// Convert to a arrayvar byteArray = new Uint8Array(responseData);var array = [];for(var i = 0; i \< byteArray.byteLength; i++) {array.push(byteArray[i]);}// Send chunks to PebbletransmitImage(array);} ``` Now that the image data has been converted, the transmission to Pebble can begin. At a high level, the JS side transmits the image data in chunks, using an incremental array index to coordinate saving of data on the C side in a mirror array. In downloading the image data, the following keys are used for the specified purposes: | Key | Purpose | | --- | --- | | `Index` | The array index that the current chunk should be stored at. This gets larger as each chunk is transmitted. | | `DataLength` | This length of the entire data array to be downloaded. As the image is compressed, this is _not_ the product of the width and height of the image. | | `DataChunk` | The chunk's image data. | | `ChunkSize` | The size of this chunk. | | `Complete` | Used to signify that the image transfer is complete. | The first message in the sequence should tell the C side how much memory to allocate to store the compressed image data: ``` function transmitImage(array) {var index = 0;var arrayLength = array.length;// Transmit the length for array allocationPebble.sendAppMessage({'DataLength': arrayLength}, function(e) {// Success, begin sending chunkssendChunk(array, index, arrayLength);}, function(e) {console.log('Failed to initiate image transfer!');})} ``` If this message is successful, the transmission of actual image data commences with the first call to `sendChunk()`. This function calculates the size of the next chunk (the smallest of either the size of the `AppMessage` inbox buffer, or the remainder of the data) and assembles the dictionary containing the index in the array it is sliced from, the length of the chunk, and the actual data itself: ``` function sendChunk(array, index, arrayLength) {// Determine the next chunk sizevar chunkSize = BUFFER\_SIZE;if(arrayLength - index \< BUFFER\_SIZE) {// Resize to fit just the remaining data itemschunkSize = arrayLength - index;}// Prepare the dictionaryvar dict = {'DataChunk': array.slice(index, index + chunkSize),'ChunkSize': chunkSize,'Index': index};// Send the chunkPebble.sendAppMessage(dict, function() {// Successindex += chunkSize;if(index \< arrayLength) {// Send the next chunksendChunk(array, index, arrayLength);} else {// Complete!Pebble.sendAppMessage({'Complete': 0});}}, function(e) {console.log('Failed to send chunk with index ' + index);});} ``` After each chunk is sent, the index is incremented with the size of the chunk that was just sent, and compared to the total length of the array. If the index exceeds the size of the array, the loop has sent all the data (this could be just a single chunk if the array is smaller than the maximum message size). The `AppKeyComplete` key is sent to inform the C side that the image is complete and ready for display. ### Receiving Compressed PNG Data In the previous section, the process for using PebbleKit JS to download and transmit an image to the C side was discussed. The process for storing and displaying this data is discussed here. Only when both parts work in harmony can an image be successfully shown from the web. The majority of the process takes place within the watchapp's [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") handler, with the presence of each key being detected and the appropriate actions taken to reconstruct the image. The first item expected is the total size of the data to be transferred. This is recorded (for later use with [`gbitmap_create_from_png_data()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_from_png_data "gbitmap\_create\_from\_png\_data")) and the buffer used to store the chunks is allocated to this exact size: ``` static uint8\_t \*s\_img\_data;static int s\_img\_size; ``` ``` // Get the received image chunkTuple \*img\_size\_t = dict\_find(iter, MESSAGE\_KEY\_DataLength);if(img\_size\_t) {s\_img\_size = img\_size\_t-\>value-\>int32;// Allocate buffer for image dataimg\_data = (uint8\_t\*)malloc(s\_img\_size \* sizeof(uint8\_t));} ``` When the message containing the data size is acknowledged, the JS side begins sending chunks with `sendChunk()`. When one of these subsequent messages is received, the three keys (`DataChunk`, `ChunkSize`, and `Index`) are used to store that chunk of data at the correct offset in the array: ``` // An image chunkTuple \*chunk\_t = dict\_find(iter, MESSAGE\_KEY\_DataChunk);if(chunk\_t) {uint8\_t \*chunk\_data = chunk\_t-\>value-\>data;Tuple \*chunk\_size\_t = dict\_find(iter, MESSAGE\_KEY\_ChunkSize);int chunk\_size = chunk\_size\_t-\>value-\>int32;Tuple \*index\_t = dict\_find(iter, MESSAGE\_KEY\_Index);int index = index\_t-\>value-\>int32;// Save the chunkmemcpy(&s\_img\_data[index], chunk\_data, chunk\_size);} ``` Finally, once the array index exceeds the size of the data array on the JS side, the `AppKeyComplete` key is transmitted, triggering the data to be transformed into a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"): ``` static BitmapLayer \*s\_bitmap\_layer;static GBitmap \*s\_bitmap; ``` ``` // Complete?Tuple \*complete\_t = dict\_find(iter, MESSAGE\_KEY\_Complete);if(complete\_t) {// Create new GBitmap from downloaded PNG datas\_bitmap = gbitmap\_create\_from\_png\_data(s\_img\_data, s\_img\_size);// Show the imageif(s\_bitmap) {bitmap\_layer\_set\_bitmap(s\_bitmap\_layer, s\_bitmap);} else {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Error creating GBitmap from PNG data!");}} ``` The final result is a compressed PNG image downloaded from the web displayed in a Pebble watchapp. ![watch](https://developer.repebble.com/assets/images/guides/pebble-apps/communications/html-5-watch.png) Get the complete source code for this example from the [`png-download-example`](https://github.com/pebble-examples/png-download-example) repository on GitHub. --- # Communication Source: https://developer.repebble.com/guides/communication/ All Pebble watchapps and watchfaces have the ability to communicate with the outside world through its connection to the user's phone. The PebbleKit collection of libraries (see below) is available to facilitate this communication between watchapps and phone apps. Examples of additional functionality made possible through PebbleKit include, but are not limited to apps that can: - Display weather, news, stocks, etc. - Communicate with other web services. - Read and control platform APIs and features of the connected phone. ## Contents - [**Advanced Communication**](https://developer.repebble.com/guides/communication/advanced-communication.md) - Details of communication tips and best practices for more advanced scenarios. - [**Datalogging**](https://developer.repebble.com/guides/communication/datalogging.md) - Information on how to collect data batches using the Datalogging API. - [**PebbleKit Android**](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) - How to use PebbleKit to communicate with a watchapp on Android. - [**PebbleKit JS**](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) - How to use PebbleKit JS to communicate with the connected phone's JS environment, and how the Pebble mobile app runs it on Android and iOS. - [**PebbleKit iOS**](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) - How to use PebbleKit to communicate with a watchapp on iOS. - [**Sending and Receiving Data**](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md) - How to send and receive data between your watchapp and phone. - [**Sports API**](https://developer.repebble.com/guides/communication/using-the-sports-api.md) - How to use the PebbleKit Sports API to integrate your mobile sports app with Pebble. ## Communication Model Pebble communicates with the connected phone via the Bluetooth connection, which is the same connection that delivers notifications and other alerts in normal use. Developers can leverage this connection to send and receive arbitrary data using the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") API. Depending on the requirements of the app, there are three possible ways to receive data sent from Pebble on the connected phone: - [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) - A JavaScript environment running within the official Pebble mobile app with web, geolocation, and extended storage access. - [_PebbleKit Android_](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) - A library available to use in Android companion apps that allows them to interact with standard Android platform APIs. - [_PebbleKit iOS_](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) - As above, but for iOS companion apps. **Important** PebbleKit JS cannot be used in conjunction with PebbleKit Android or PebbleKit iOS. All messages sent from a Pebble watchapp or watchface will be delivered to the appropriate phone app depending on the layout of the developer's project: - If at least an `index.js` file is present in `src/pkjs/`, the message will be handled by PebbleKit JS. - If there is no valid JS file present (at least an `index.js`) in the project, the message will be delivered to the official Pebble mobile app. If there is a companion app installed that has registered a listener with the same UUID as the watchapp, the message will be forwarded to that app via PebbleKit Android/iOS. --- # Datalogging Source: https://developer.repebble.com/guides/communication/datalogging/ In addition to the more realtime [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") API, the Pebble SDK also includes the [`Datalogging`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md "DataLogging") API. This is useful for applications where data can be sent in batches at time intervals that make the most sense (for example, to save battery power by allowing the watch to spend more time sleeping). Datalogging also allows upto 640 kB of data to be buffered on the watch until a connection is available, instead of requiring a connection be present at all times. If data is logged while the watch is disconnected, it will be transferred to the Pebble mobile app in batches for processing at the next opportunity. The data is then passed on to any [_PebbleKit Android_](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) or [_PebbleKit iOS_](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) companion app that wishes to process it. ## Collecting Data Datalogging can capture any values that are compatible with one of the [`DataLoggingItemType`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingItemType "DataLoggingItemType") `enum` (byte array, unsigned integer, and integer) values, with common sources including accelerometer data or compass data. ### Creating a Session Data is logged to a 'session' with a unique identifier or tag, which allows a single app to have multiple data logs for different types of data. First, define the identifier(s) that should be used where appropriate: ``` // The log's ID. Only one required in this example#define TIMESTAMP\_LOG 1 ``` Next, a session must first be created before any data can be logged to it. This should be done during app initialization, or just before the first time an app needs to log some data: ``` // The session reference variablestatic DataLoggingSessionRef s\_session\_ref; ``` ``` static void init() {// Begin the sessions\_session\_ref = data\_logging\_create(TIMESTAMP\_LOG, DATA\_LOGGING\_INT, sizeof(int), true);/\* ... \*/} ``` > Note: The final parameter of [`data_logging_create()`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#data_logging_create "data\_logging\_create") allows a previous log session to be continued, instead of starting from screatch on each app launch. ### Logging Data Once the log has been created or resumed, data collection can proceed. Each call to [`data_logging_log()`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#data_logging_log "data\_logging\_log") will add a new entry to the log indicated by the [`DataLoggingSessionRef`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingSessionRef "DataLoggingSessionRef") variable provided. The success of each logging operation can be checked using the [`DataLoggingResult`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingResult "DataLoggingResult") returned: ``` const int value = 16;const uint32\_t num\_values = 1;// Log a single valueDataLoggingResult result = data\_logging\_log(s\_session\_ref, &value, num\_values);// Was the value successfully stored? If it failed, print the reasonif(result != DATA\_LOGGING\_SUCCESS) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Error logging data: %d", (int)result);} ``` ### Finishing a Session Once all data has been logged or the app is exiting, the session must be finished to signify that the data is to be either transferred to the connected phone (if available), or stored for later transmission. ``` // Finish the session and sync data if appropriatedata\_logging\_finish(s\_session\_ref); ``` > Note: Once a session has been finished, data cannot be logged to its [`DataLoggingSessionRef`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingSessionRef "DataLoggingSessionRef") until it is resumed or began anew. ## Receiving Data > Note: Datalogging data cannot be received via PebbleKit JS. Data collected with the [`Datalogging`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md "DataLogging") API can be received and processed in a mobile companion app using PebbleKit Android or PebbleKit iOS. This enables it to be used in a wide range of general applications, such as detailed analysis of accelerometer data for health research, or transmission to a third-party web service. ### With PebbleKit Android PebbleKit Android allows collection of data by registering a `PebbleDataLogReceiver` within your `Activity` or `Service`. When creating a receiver, be careful to provide the correct UUID to match that of the watchapp that is doing that data collection. For example: ``` // The UUID of the watchappprivate UUID APP\_UUID = UUID.fromString("64fcb54f-76f0-418a-bd7d-1fc1c07c9fc1"); ``` Use the following overridden methods to collect data and determine when the session has been finished by the watchapp. In the example below, each new integer received represents the uptime of the watchapp, and is displayed in an Android `TextView`: ``` // Create a receiver to collect logged dataPebbleKit.PebbleDataLogReceiver dataLogReceiver = new PebbleKit.PebbleDataLogReceiver(APP\_UUID) {@Overridepublic void receiveData(Context context, UUID logUuid, Long timestamp, Long tag, int data) {// super() (removed from IDE-generated stub to avoid exception)Log.i(TAG, "New data for session " + tag + "!");// Cumulatively add the new data item to a TextView's current textString current = dataView.getText().toString();current += timestamp.toString() + ": " + data + "s since watchapp launch.\n";dataView.setText(current);}@Overridepublic void onFinishSession(Context context, UUID logUuid, Long timestamp, Long tag) {Log.i(TAG, "Session " + tag + " finished!");}};// Register the receiverPebbleKit.registerDataLogReceiver(getApplicationContext(), dataLogReceiver); ``` **Important** If your Java IDE automatically adds a line of code to call super() when you create the method, the code will result in an UnsupportedOperationException. Ensure you remove this line to avoid the exception. Once the `Activity` or `Service` is closing, you should attempt to unregister the receiver. However, this is not always required (and will cause an exception to be thrown if invoked when not required), so use a `try, catch` statement: ``` @Overrideprotected void onPause() {super.onPause();try {unregisterReceiver(dataLogReceiver);} catch(Exception e) {Log.w(TAG, "Receiver did not need to be unregistered");}} ``` ### With PebbleKit iOS The process of collecing data via a PebbleKit iOS companion mobile app is similar to that of using PebbleKit Android. Once your app is a delegate of [`PBDataLoggingServiceDelegate`](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md "PBDataLoggingServiceDelegate") (see [_PebbleKit iOS_](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) for details), simply register the class as a datalogging delegate: ``` // Get datalogging data by becoming the delegate[[PBPebbleCentral defaultCentral] dataLoggingServiceForAppUUID:myAppUUID].delegate = self; ``` Being a datalogging delegate allows the class to receive two additional [callbacks](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md) for when new data is available, and when the session has been finished by the watch. Implement these callbacks to read the new data: ``` -(BOOL)dataLoggingService:(PBDataLoggingService \*)servicehasSInt32s:(const SInt32 [])datanumberOfItems:(UInt16)numberOfItemsforDataLog:(PBDataLoggingSessionMetadata \*)log {NSLog(@"New data received!");// Append newest data to displayed stringNSString \*current = self.dataView.text;NSString \*newString = [NSString stringWithFormat:@"New item: %d", data[0]];current = [current stringByAppendingString:newString];self.dataView.text = current;return YES;}-(void)dataLoggingService:(PBDataLoggingService \*)servicelogDidFinish:(PBDataLoggingSessionMetadata \*)log {NSLog(@"Finished data log: %@", log);} ``` ### Special Considerations for iOS Apps - The logic to deal with logs with the same type of data (i.e., the same tag/type) but from different sessions (different timestamps) must be created by the developer using the delegate callbacks. - To check whether the data belongs to the same log or not, use `-isEqual:` on `PBDataLoggingSessionMetadata`. For convenience, `PBDataLoggingSessionMetadata` can be serialized using `NSCoding`. - Using multiple logs in parallel (for example to transfer different kinds of information) will require extra logic to re-associate the data from these different logs, which must also be implemented by the developer. --- # PebbleKit Android Source: https://developer.repebble.com/guides/communication/using-pebblekit-android/ # PebbleKit Android 2 Currently recommended way of communicating with the watch is to use PebbleKit Android 2. See [its documentation](https://github.com/pebble-dev/PebbleKitAndroid2) for more information. # Legacy PebbleKit Android 1 [PebbleKit Android](https://github.com/pebble/pebble-android-sdk/) is a Java library that works with the Pebble SDK and can be embedded in any Android application. Using the classes and methods in this library, an Android companion app can find and exchange data with a Pebble watch. This section assumes that the reader is familiar with basic Android development and Android Studio as an integrated development environment. Refer to the [Android Documentation](http://developer.android.com/sdk/index.html) for more information on installing the Android SDK. Most PebbleKit Android methods require a `Context` parameter. An app can use `getApplicationContext()`, which is available from any `Activity` implementation. ### Setting Up PebbleKit Android Add PebbleKit Android to an Android Studio project in the `app/build.gradle` file: ``` dependencies { compile 'com.getpebble:pebblekit:4.0.1' } ``` ### Sending Messages from Android Since Android apps are built separately from their companion Pebble apps, there is no way for the build system to automatically create matching appmessage keys. You must therefore manually specify them in `package.json`, like so: ``` {"ContactName": 0,"Age": 1} ``` These numeric values can then be used as appmessage keys in your Android app. Messages are constructed with the `PebbleDictionary` class and sent to a C watchapp or watchface using the `PebbleKit` class. The first step is to create a `PebbleDictionary` object: ``` // Create a new dictionaryPebbleDictionary dict = new PebbleDictionary(); ``` Data items are added to the [`PebbleDictionary`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md) using key-value pairs with the methods made available by the object, such as `addString()` and `addInt32()`. An example is shown below: ``` // The key representing a contact name is being transmittedfinal int AppKeyContactName = 0;final int AppKeyAge = 1;// Get data from the appfinal String contactName = getContact();final int age = getAge();// Add data to the dictionarydict.addString(AppKeyContactName, contactName);dict.addInt32(AppKeyAge, age); ``` Finally, the dictionary is sent to the C app by calling `sendDataToPebble()` with a UUID matching that of the C app that will receive the data: ``` final UUID appUuid = UUID.fromString("EC7EE5C6-8DDF-4089-AA84-C3396A11CC95");// Send the dictionaryPebbleKit.sendDataToPebble(getApplicationContext(), appUuid, dict); ``` Once delivered, this dictionary will be available in the C app via the [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") callback, as detailed in [_Sending and Receiving Data_](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md#inbox-received). ### Receiving Messages on Android Receiving messages from Pebble in a PebbleKit Android app requires a listener to be registered in the form of a `PebbleDataReceiver` object, which extends `BroadcastReceiver`: ``` // Create a new receiver to get AppMessages from the C appPebbleDataReceiver dataReceiver = new PebbleDataReceiver(appUuid) {@Overridepublic void receiveData(Context context, int transaction\_id,PebbleDictionary dict) {// A new AppMessage was received, tell PebblePebbleKit.sendAckToPebble(context, transaction\_id);}}; ``` **Important** PebbleKit apps **must** manually send an acknowledgement (Ack) to Pebble to inform it that the message was received successfully. Failure to do this will cause timeouts. Once created, this receiver should be registered in `onResume()`, overridden from `Activity`: ``` @Overridepublic void onResume() {super.onResume();// Register the receiverPebbleKit.registerReceivedDataHandler(getApplicationContext(), dataReceiver);} ``` > Note: To avoid getting callbacks after the `Activity` or `Service` has exited, apps should attempt to unregister the receiver in `onPause()` with `unregisterReceiver()`. With a receiver in place, data can be read from the provided [`PebbleDictionary`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md) using analogous methods such as `getString()` and `getInteger()`. Before reading the value of a key, the app should first check that it exists using a `!= null` check. The example shown below shows how to read an integer from the message, in the scenario that the watch is sending an age value to the Android companion app: ``` @Overridepublic void receiveData(Context context, int transaction\_id,PebbleDictionary dict) {// If the tuple is present...Long ageValue = dict.getInteger(AppKeyAge);if(ageValue != null) {// Read the integer valueint age = ageValue.intValue();}} ``` ### Other Capabilities In addition to sending and receiving messages, PebbleKit Android also allows more intricate interactions with Pebble. See the [PebbleKit Android Documentation](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md) for a complete list of available methods. Some examples are shown below of what is possible: - Checking if the watch is connected: - Registering for connection events with `registerPebbleConnectedReceiver()` and `registerPebbleDisconnectedReceiver()`, and a suitable `BroadcastReceiver`. - Registering for Ack/Nack events with `registerReceivedAckHandler()` and `registerReceivedNackHandler()`. - Launching and killing the watchapp with `startAppOnPebble()` and `closeAppOnPebble()`. --- # PebbleKit JS Source: https://developer.repebble.com/guides/communication/using-pebblekit-js/ PebbleKit JS allows a JavaScript component (run in a sandbox inside the official Pebble mobile app) to be added to any watchapp or watchface in order to extend the functionality of the app beyond what can be accomplished on the watch itself. Extra features available to an app using PebbleKit JS include: - Access to extended storage with [`localStorage`](#using-localstorage). - Internet access using [`XMLHttpRequest`](#using-xmlhttprequest). - Location data using [`geolocation`](#using-geolocation). - The ability to show a configuration page to allow users to customize how the app behaves. This is discussed in detail in [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md). ## Setting Up PebbleKit JS can be set up by creating the `index.js` file in the project's `src/pkjs/` directory. Code in this file will be executed when the associated watchapp is launched, and will stop once that app exits. The basic JS code required to begin using PebbleKit JS is shown below. An event listener is created to listen for the `ready` event - fired when the watchapp has been launched and the JS environment is ready to receive messages. This callback must return within a short space of time (a few seconds) or else it will timeout and be killed by the phone. ``` Pebble.addEventListener('ready', function() {// PebbleKit JS is ready!console.log('PebbleKit JS ready!');}); ``` **Important** A watchapp or watchface **must** wait for the `ready` event before attempting to send messages to the connected phone. See [_Advanced Communication_](https://developer.repebble.com/guides/communication/advanced-communication.md#waiting-for-pebblekit-js) to learn how to do this. ## Defining Keys Before any messages can be sent or received, the keys to be used to store the data items in the dictionary must be declared. The watchapp side uses exclusively integer keys, whereas the JavaScript side may use the same integers or named string keys declared in `package.json`. Any string key not declared beforehand will not be transmitted to/from Pebble. > Note: This requirement is true of PebbleKit JS **only** , and not PebbleKit Android or iOS. Keys are declared in the project's `package.json` file in the `messageKeys` object, which is inside the `pebble` object. Example keys are shown as equivalents to the ones used in the hypothetical weather app example in [_Sending and Receiving Data_](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md#choosing-keys). ``` "messageKeys": ["Temperature","WindSpeed","WindDirection","RequestData","LocationName"] ``` The names chosen here will be injected into your C code prefixed with `MESSAGE_KEY_`, like `MESSAGE_KEY_Temperature`. As such, they must be legal C identifiers. If you want to emulate an array by attaching multiple "keys" to a name, you can specify the size of the array by adding it in square brackets: for instance, `"LapTimes[10]`" would create a key called `LapTimes` and leave nine empty keys after it which can be accessed by arithmetic, e.g. `MESSAGE_KEY_LapTimes + 3`. ## Sending Messages from JS Messages are sent to the C watchapp or watchface using `Pebble.sendAppMessage()`, which accepts a standard JavaScript object containing the keys and values to be transmitted. The keys used **must** be identical to the ones declared earlier. An example is shown below: ``` // Assemble data objectvar dict = {'Temperature': 29,'LocationName': 'London, UK'};// Send the objectPebble.sendAppMessage(dict, function() {console.log('Message sent successfully: ' + JSON.stringify(dict));}, function(e) {console.log('Message failed: ' + JSON.stringify(e));}); ``` It is also possible to read the numeric values of the keys by `require`ing `message_keys`, which is necessary to use the array feature. For instance: ``` // Require the keys' numeric values.var keys = require('message\_keys');// Build a dictionary.var dict = {}dict[keys.LapTimes] = 42dict[keys.LapTimes+1] = 51// Send the objectPebble.sendAppMessage(dict, function() {console.log('Message sent successfully: ' + JSON.stringify(dict));}, function(e) {console.log('Message failed: ' + JSON.stringify(e));}); ``` ### Type Conversion Depending on the type of the item in the object to be sent, the C app will be able to read the value (from the [`Tuple.value` union](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md#data-types)) according to the table below: | JS Type | Union member | | --- | --- | | String | cstring | | Number | int32 | | Array | data | | Boolean | int16 | ## Receiving Messages in JS When a message is received from the C watchapp or watchface, the `appmessage` event is fired in the PebbleKit JS app. To receive these messages, register the appropriate event listener: ``` // Get AppMessage eventsPebble.addEventListener('appmessage', function(e) {// Get the dictionary from the messagevar dict = e.payload;console.log('Got message: ' + JSON.stringify(dict));}); ``` Data can be read from the dictionary by reading the value if it is present. A suggested best practice involves first checking for the presence of each key within the callback using an `if()` statement. ``` if(dict['RequestData']) {// The RequestData key is present, read the valuevar value = dict['RequestData'];} ``` ## Using LocalStorage In addition to the storage available on the watch itself through the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API, apps can take advantage of the larger storage on the connected phone through the use of the HTML 5 [`localStorage`](http://www.w3.org/TR/webstorage/) API. Data stored here will persist across app launches, and so can be used to persist latest data, app settings, and other data. PebbleKit JS `localStorage` is: - Associated with the application UUID and cannot be shared between apps. - Persisted when the user uninstalls and then reinstalls an app. - Persisted when the user upgrades an app. To store a value: ``` var color = '#FF0066';// Store some datalocalStorage.setItem('backgroundColor', color); ``` To read the data back: ``` var color = localStorage.getItem('backgroundColor'); ``` > Note: Keys used with `localStorage` should be Strings. ## Using XMLHttpRequest A PebbleKit JS-equipped app can access the internet and communicate with web services or download data using the standard [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object. To communicate with the web, create an `XMLHttpRequest` object and send it, specifying the HTTP method and URL to be used, as well as a callback for when it is successfully completed: ``` var method = 'GET';var url = 'http://example.com';// Create the requestvar request = new XMLHttpRequest();// Specify the callback for when the request is completedrequest.onload = function() {// The request was successfully completed!console.log('Got response: ' + this.responseText);};// Send the requestrequest.open(method, url);request.send(); ``` If the response is expected to be in the JSON format, data items can be easily read after the `responseText` is converted into a JSON object: ``` request.onload = function() {try {// Transform in to JSONvar json = JSON.parse(this.responseText);// Read datavar temperature = json.main.temp;} catch(err) {console.log('Error parsing JSON response!');}}; ``` ## Using Geolocation PebbleKit JS provides access to the location services provided by the phone through the [`navigator.geolocation`](http://dev.w3.org/geo/api/spec-source.html) object. Declare that the app will be using the `geolocation` API by adding the string `location` in the `capabilities` array in `package.json`: ``` "capabilities": ["location"] ``` Below is an example showing how to get a single position value from the `geolocation` API using the [`getCurrentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) method: ``` function success(pos) {console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude);}function error(err) {console.log('location error (' + err.code + '): ' + err.message);}/\* ... \*/// Choose options about the data returnedvar options = {enableHighAccuracy: true,maximumAge: 10000,timeout: 10000};// Request current positionnavigator.geolocation.getCurrentPosition(success, error, options); ``` Location permission is given by the user to the Pebble application for all Pebble apps. The app should gracefully handle the `PERMISSION DENIED` error and fallback to a default value or manual configuration when the user has denied location access to Pebble apps. ``` function error(err) {if(err.code == err.PERMISSION\_DENIED) {console.log('Location access was denied by the user.');} else {console.log('location error (' + err.code + '): ' + err.message);}} ``` The `geolocation` API also provides a mechanism to receive callbacks when the user's position changes to avoid the need to manually poll at regular intervals. This is achieved by using [`watchPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition) in a manner similar to the example below: ``` // An ID to store to later clear the watchvar watchId;function success(pos) {console.log('Location changed!');console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude);}function error(err) {console.log('location error (' + err.code + '): ' + err.message);}/\* ... \*/var options = {enableHighAccuracy: true,maximumAge: 0,timeout: 5000};// Get location updateswatchId = navigator.geolocation.watchPosition(success, error, options); ``` To cancel the update callbacks, use the `watchId` variable received when the watch was registered with the [`clearWatch()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/clearWatch) method: ``` // Clear the watch and stop receiving updatesnavigator.geolocation.clearWatch(watchId); ``` ## Account Token PebbleKit JS provides a unique account token that is associated with the Pebble account of the current user, accessible using `Pebble.getAccountToken()`: ``` // Get the account tokenconsole.log('Pebble Account Token: ' + Pebble.getAccountToken()); ``` The token is a string with the following properties: - From the developer's perspective, the account token of a user is identical across platforms and across all the developer's watchapps. - If the user is not logged in, the token will be an empty string (''). ## Watch Token PebbleKit JS also provides a unique token that can be used to identify a Pebble device. It works in a similar way to `Pebble.getAccountToken()`: ``` // Get the watch tokenconsole.log('Pebble Watch Token: ' + Pebble.getWatchToken()); ``` The token is a string derived from the watch and the developer ID of an appstore app, or the UUID of a sideloaded app, so apps from the same developer see the same watch token and apps from different developers do not. **Important** The watch token is dependent on the watch's serial number, and therefore **should not** be used to store sensitive user information in case the watch changes ownership. If the app wishes to track a specific user _and_ watch, use a combination of the watch and account token. ## Showing a Notification A PebbleKit JS app can send a notification to the watch. This uses the standard system notification layout with customizable `title` and `body` fields: ``` var title = 'Update Available';var body = 'Version 1.5 of this app is now available from the appstore!';// Show the notificationPebble.showSimpleNotificationOnPebble(title, body); ``` > Note: PebbleKit Android/iOS applications cannot directly invoke a notification, and should instead leverage the respective platform notification APIs. These will be passed on to Pebble unless the user has turned them off in the mobile app. ## Getting Watch Information Use `Pebble.getActiveWatchInfo()` to return an object of data about the connected Pebble. This API is currently only available for SDK 3.0 and above. Do not assume that this function exists, so test that it is available before attempting to use it using the code shown below. ``` var watch = Pebble.getActiveWatchInfo ? Pebble.getActiveWatchInfo() : null;if(watch) {// Information is available!} else {// Not available, handle gracefully} ``` > Note: If there is no active watch available, `null` will be returned. The table below details the fields of the returned object and the information available. | Field | Type | Description | Values | | --- | --- | --- | --- | | `platform` | String | Hardware platform name. | `aplite`, `basalt`, `chalk`. | | `model` | String | Watch model name including color. | `pebble_black`, `pebble_grey`, `pebble_white`, `pebble_red`, `pebble_orange`, `pebble_blue`, `pebble_green`, `pebble_pink`, `pebble_steel_silver`, `pebble_steel_black`, `pebble_time_red`, `pebble_time_white`, `pebble_time_black`, `pebble_time_steel_black`, `pebble_time_steel_silver`, `pebble_time_steel_gold`, `pebble_time_round_silver_14mm`, `pebble_time_round_black_14mm`, `pebble_time_round_rose_gold_14mm`, `pebble_time_round_silver_20mm`, `pebble_time_round_black_20mm`, `qemu_platform_aplite`, `qemu_platform_basalt`, `qemu_platform_chalk`. | | `language` | String | Language currently selected on the watch. | E.g.: `en_GB`. See the [_Internationalization_](https://developer.repebble.com/guides/tools-and-resources/internationalization.md#locales-supported-by-pebble) for more information. | | `firmware` | Object | The firmware version running on the watch. | See below for sub-fields. | | `firmware.major` | Number | Major firmware version. | E.g.: `2` | | `firmware.minor` | Number | Minor firmware version. | E.g.: `8` | | `firmware.patch` | Number | Patch firmware version. | E.g.: `1` | | `firmware.suffix` | String | Any additional firmware versioning. | E.g.: `beta3` | ## Differences in the Pebble Mobile App The Pebble mobile app runs PebbleKit JS differently on each phone platform. On Android the JavaScript runs in a hidden WebView, so the full set of browser APIs is available. On iOS it runs in JavaScriptCore with a small set of APIs added by the app: `XMLHttpRequest`, `WebSocket`, `setTimeout` and `setInterval`, `localStorage`, `atob` and `btoa`, `navigator.geolocation`, and `console`. Anything else that a browser provides, such as `fetch`, `Blob` or the DOM, does not exist on iOS. Code that follows the sections above works on both. The sections below list the behaviour that differs from the original Pebble apps, or between Android and iOS, so that existing apps can be checked against it. The source is at [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp). ### Lifecycle The JavaScript for an app starts when the app starts running on the watch and stops when the app exits. Launching the app again, or reconnecting the watch, starts a fresh JavaScript context, so state that must survive belongs in `localStorage`. Only one app's JavaScript runs at a time. On iOS the `ready` event fires as soon as the app's script has been evaluated. On Android it fires once the script has loaded in the WebView. Messages from the watch that arrive before `ready` are held for up to six seconds. On iOS the phone suspends the Pebble mobile app when it is in the background and no Bluetooth activity is pending. Timers do not fire while the app is suspended and run late when it resumes, so do not rely on `setInterval` for work that must happen while the user is not looking at the phone. ### Network Requests Use `XMLHttpRequest`. It is the WebView's own implementation on Android and the app's implementation on iOS, where these limits apply: - Event handlers receive an event with only a `type` property. Read the result from the request object, not from `event.target`. - `responseType` can be `""`, `"text"`, `"json"` or `"arraybuffer"`. `"blob"` and `"document"` throw. An `arraybuffer` response is delivered as a `Uint8Array`. - There is no `timeout` property and there are no progress events. - Synchronous requests (`async` set to `false` in `open()`) still deliver their response asynchronously, so the response is not available when `send()` returns. Always use asynchronous requests. `fetch()` is only available on Android. Apps that use it fail on iOS with `fetch is not defined`. `WebSocket` is available on both platforms. On iOS `binaryType` is always `"arraybuffer"`. ### Storage `localStorage` is separate for each app, keyed by the app's UUID, and keeps its contents across app launches and phone app restarts. Values are stored as strings, as in a browser. On Android, values written through the `localStorage` object are saved when the app's JavaScript stops. Use `localStorage.setItem()` rather than assigning properties so that writes are not lost if the phone app is killed. ### Geolocation `navigator.geolocation.getCurrentPosition()` and `watchPosition()` work on both platforms with these differences from a browser: - `maximumAge` (default 0) and `timeout` (default 15 seconds) are honoured. When the timeout passes and a previous position is known, that position is returned instead of an error. - `enableHighAccuracy` only affects `watchPosition()`. - The position object has `coords` with `latitude`, `longitude`, `accuracy`, `altitude`, `heading` and `speed`, and no `timestamp`. - Errors always have `code` set to `1` and a `message` string. ### Tokens `Pebble.getAccountToken()` and `Pebble.getWatchToken()` are derived from the signed-in Pebble account (or, for the watch token, the watch serial) together with the developer ID of an appstore app or the UUID of a sideloaded app. Both tokens therefore change when an app that was tested by sideloading is installed from the appstore. Treat data stored against a token from a sideloaded build as test data. `Pebble.getTimelineToken()` returns the token the appstore issued for the app. A sideloaded app gets a placeholder token while the _Emulate Timeline Webservice_ setting is on, which it is by default, and the failure callback when it is off. Neither token can be used with a server: the Pebble mobile app does not sync pins, so use [_local pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) instead. ### Watch Information `Pebble.getActiveWatchInfo()` returns `platform`, `model`, `language` and `firmware` (`major`, `minor`, `patch`, `suffix`). The `platform` value is the connected watch's platform only when that platform is in the app's `targetPlatforms`. When the watch is running a build for an older platform, `platform` reports the platform of the build that is installed: | Connected watch | Reported when not a target | | --- | --- | | flint (Pebble 2 Duo) | diorite if targeted, otherwise aplite | | emery (Pebble Time 2) | basalt | | gabbro (Pebble Round 2) | chalk | Add the new platforms to `targetPlatforms` and rebuild to receive their real names. ### AppMessage `Pebble.sendAppMessage()` follows these rules: - Keys must be names declared in `messageKeys` in `package.json`, or numeric strings. Keys that are not declared are dropped without an error. - Values can be strings, integers, booleans (sent as 0 or 1) and arrays of numbers (sent as a byte array). Fractional numbers are truncated to integers. Nested objects are not supported and stop the message from being sent. - The call fails with `"Failed to connect to Pebble"` if the watch does not accept the message within five seconds. If the watch does not acknowledge the message within ten seconds, the failure callback receives an error of `"nack"`. ### Configuration Pages `Pebble.openURL()` is only honoured in response to the `showConfiguration` event, and must be called within ten seconds of it. The page opens in a WebView inside the Pebble mobile app, not in the system browser. `data:` URLs are accepted, so a configuration page can be embedded in the app's JavaScript. The page returns its result by navigating to `pebblejs://close#`. `pebblejs://close/?` and `pebblejs://close/` are also accepted; `pebblejs://close?data=...` is not. The data is percent-decoded once and passed to the `webviewclosed` handler as `e.response`. It is not parsed, so call `JSON.parse(e.response)` as before. If the page is closed without data, or the user leaves it with the back button, `webviewclosed` does not fire. Read [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) for the full flow. ### Plugins (Preview) The `Pebble` object has four functions for the plugins preview. They do nothing useful until the preview is turned on in the Pebble mobile app, which is off by default, and the API will change; read [_Plugins (Preview)_](https://developer.repebble.com/guides/plugins.md) before using them. - `Pebble.enumeratePlugins()` returns an array of the installed plugins with their sources and actions. - `Pebble.subscribeToSource(config)` subscribes to a category and item and delivers updates to `config.onData`. - `Pebble.invokeAction(config)` runs one action on one plugin and returns a Promise that resolves with the result. - `Pebble.sendConfigMessage(message)` pushes a message to the app's own settings page while it is open. ### Other Differences - `Pebble.showToast()` shows a toast on Android and does nothing on iOS. - `Pebble.getVersionCode()` is not available. Calling it throws. - `Pebble.timelineSubscribe()`, `timelineUnsubscribe()`, `timelineSubscriptions()` and `appGlanceReload()` do nothing and never call back. Server-pushed pins and glances are not supported. - `console.log()` output appears in `pebble logs` prefixed with the app's name and the line number. The Pebble mobile app's own log, the one included in bug reports, replaces lines containing words such as "token" or "location" with `` when the setting that obfuscates log content is on; the Developer Connection log is not redacted. --- # PebbleKit iOS Source: https://developer.repebble.com/guides/communication/using-pebblekit-ios/ > **Important: PebbleKit iOS has been discontinued.** Apple has made it increasingly difficult for third-party smartwatches to provide a competitive experience on iOS. Over the years, Apple has systematically restricted capabilities that were essential to Pebble's functionality, including the ability to reply to notifications, send messages, enable inter-app communication, and properly function when the companion app is in the background. These restrictions have made it nearly impossible to build a smartwatch experience on iOS that matches the capabilities available on Android. We encourage developers to focus on Android development where Pebble can deliver its full potential without artificial platform limitations. Learn more about Apple's restrictions and their impact on third-party wearables in [this blog post](https://ericmigi.com/blog/apple-restricts-pebble-from-being-awesome-with-iphones). This section assumes that the reader has a basic knowledge of Objective-C, Xcode as an IDE, and the delegate and block patterns. > PebbleKit iOS should be compatible if your app uses Swift. The framework itself is written in Objective-C to avoid the requirement of the Swift runtime in pure Objective-C apps, and to improve the backwards and forwards compatibility. ### Setting Up PebbleKit iOS If the project is using [CocoaPods](http://cocoapods.org/) (which is the recommended approach), just add `pod 'PebbleKit'` to the `Podfile` and execute `pod install`. After installing PebbleKit iOS in the project, perform these final steps: - If the iOS app needs to run in the background, you should update your target’s “Capabilities” in Xcode. Enable “Background Modes” and select both “Uses Bluetooth LE accessories” and “Acts as a Bluetooth LE accessory”. This should add the keys `bluetooth-peripheral` (“App shares data using CoreBluetooth”) and `bluetooth-central` (“App communicates using CoreBluetooth”) to your target’s `Info.plist` file. - If you are using Xcode 8 or greater (and recommended for previous versions), you must also add the key `NSBluetoothPeripheralUsageDescription` (“Privacy - Bluetooth Peripheral Usage Description”) to your `Info.plist`. > To add PebbleKit iOS manually, or some other alternatives follow the steps in the [repository](https://github.com/pebble/pebble-ios-sdk/). The documentation might also include more information that might be useful. Read it carefully. ### Targeting a Companion App Before an iOS companion app can start communicating or exchange messages with a watchapp on Pebble, it needs to give PebbleKit a way to identify the watchapp. The UUID of your watchapp is used for this purpose. Set the app UUID associated with the PBPebbleCentral instance. A simple way to create a UUID in standard representation to `NSUUID` is shown here: ``` // Set UUID of watchappNSUUID \*myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"];[PBPebbleCentral defaultCentral].appUUID = myAppUUID; ``` If you are trying to communicate with the built-in Sports or Golf apps, their UUID are available as part of PebbleKit with `PBSportsUUID` and `PBGolfUUID`. You must register those UUID if you intend to communicate with those apps. ### Becoming a Delegate To communicate with a Pebble watch, the class must implement `PBPebbleCentralDelegate`: ``` @interfaceViewController () \ ``` The `PBPebbleCentral` class should not be instantiated directly. Instead, always use the singleton provided by `[PBPebbleCentral defaultCentral]`. An example is shown below, with the Golf app UUID: ``` central = [PBPebbleCentral defaultCentral];central.appUUID = myAppUUID;[central run]; ``` Once this is done, set the class to be the delegate: ``` [PBPebbleCentral defaultCentral].delegate = self; ``` This delegate will get two callbacks: `pebbleCentral:watchDidConnect:isNew:` and `pebbleCentral:watchDidDisconnect:` every time a Pebble connects or disconnects. The app won't get connection callbacks if the Pebble is already connected when the delegate is set. Implement these to receive the associated connection/disconnection events: ``` -(void)pebbleCentral:(PBPebbleCentral \*)central watchDidConnect:(PBWatch \*)watch isNew:(BOOL)isNew {NSLog(@"Pebble connected: %@", watch.name);// Keep a reference to this watchself.connectedWatch = watch;}-(void)pebbleCentral:(PBPebbleCentral \*)central watchDidDisconnect:(PBWatch \*)watch {NSLog(@"Pebble disconnected: %@", watch.name);// If this was the recently connected watch, forget itif ([watch isEqual:self.connectedWatch]) {self.connectedWatch = nil;}} ``` ### Initiating Bluetooth Communication Once the iOS app is correctly set up to communicate with Pebble, the final step is to actually begin communication. No communication can take place until the following is called: ``` [[PBPebbleCentral defaultCentral] run]; ``` > Once this occurs, the user _may_ be shown a dialog asking for confirmation that they want the app to communicate. This means the app should not call `run:` until the appropriate moment in the UI. ### Sending Messages from iOS Since iOS apps are built separately from their companion Pebble apps, there is no way for the build system to automatically create matching app message keys. You must therefore manually specify them in `package.json`, like so: ``` {"Temperature": 0,"WindSpeed": 1,"WindDirection": 2,"RequestData": 3,"LocationName": 4} ``` These numeric values can then be used as app message keys in your iOS app. Messages are constructed with the `NSDictionary` class and sent to the C watchapp or watchface by the `PBPebbleCentralDelegate` when the `appMessagesPushUpdate:` function is invoked. To send a message, prepare an `NSDictionary` object with the data to be sent to the C watchapp. Data items are added to the [`NSDictionary`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/) using key-value pairs of standard data types. An example containing a string and an integer is shown below: ``` NSDictionary \*update = @{ @(0):[NSNumber pb\_numberWithUint8:42],@(1):@"a string" }; ``` Send this dictionary to the watchapp using `appMessagesPushUpdate:`. The first argument is the update dictionary to send and the second argument is a callback block that will be invoked when the data has been acknowledged by the watch (or if an error occurs). ``` [self.connectedWatch appMessagesPushUpdate:update onSent:^(PBWatch \*watch, NSDictionary \*update, NSError \*error) {if (!error) {NSLog(@"Successfully sent message.");} else {NSLog(@"Error sending message: %@", error);}}]; ``` Once delivered, this dictionary will be available in the C app via the [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") callback, as detailed in [_Sending and Receiving Data_](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md#inbox-received). ### Receiving Messages on iOS To receive messages from a watchapp, register a receive handler (a block) with `appMessagesAddReceiveUpdateHandler:`. This block will be invoked with two parameters - a pointer to a `PBWatch` object describing the Pebble that sent the message and an `NSDictionary` with the message received. ``` [self.connectedWatch appMessagesAddReceiveUpdateHandler:^BOOL(PBWatch \*watch, NSDictionary \*update) {NSLog(@"Received message: %@", update);// Send Ack to Pebblereturn YES;}]; ``` > Always return `YES` in the handler. This instructs PebbleKit to automatically send an ACK to Pebble, to avoid the message timing out. Data can be read from the `NSDictionary` by first testing for each key's presence using a `!= nil` check, and reading the value if it is present: ``` NSNumber \*key = @1;// If the key is present in the received dictionaryif (update[key]) {// Read the integer valueint value = [update[key] intValue];} ``` ### Other Capabilities In addition to sending and receiving messages, PebbleKit iOS also allows more intricate interactions with Pebble. See the [PebbleKit iOS Documentation](https://developer.repebble.com/docs/pebblekit-ios.md) for more information. Some examples are shown below of what is possible: - Checking if the watch is connected using the `connected` property of a `PBWatch`. ``` BOOL isConnected = self.watch.connected; ``` - Receiving `watchDidConnect` and `watchDidDisconnect` events through being a `PBDataloggingServiceDelegate`. ### Limitations of PebbleKit on iOS The iOS platform imposes some restrictions on what apps can do with accessories. It also limits the capabilities of apps that are in the background. It is critical to understand these limitations when developing an app that relies on PebbleKit iOS. On iOS, all communication between a mobile app and Pebble is managed through a communication session. This communication session is a protocol specific to iOS, with notable limitations that the reader should know and understand when developing an iOS companion app for Pebble. #### Bluetooth Low Energy (BLE) Connections For Pebble apps that communicate with Pebble in BLE mode, a session can be created for each app that requires one. This removes the 'one session only' restriction, but only for these BLE apps. Currently, there are several BLE only devices, such as Pebble Time Round, and Pebble 2, but all the devices using a firmware 3.8 or greater can use BLE to communicate with PebbleKit. For BLE apps, the 'phone must launch' restriction is removed. The iOS companion app can be restarted by the watchapp if it stops working if user force-quits iOS app, or it crashes. Note that the app will not work after rebooting iOS device, which requires it be launched by the iPhone user once after boot. #### Communication with firmware older than 3.0 PebbleKit iOS 3.1.1 is the last PebbleKit that supports communication with firmwares older than 3.0. PebbleKit iOS 4.0.0 can only communicate with Pebble devices with firmware newer than 3.0. For newer devices like Pebble Time, Pebble Time Steel, Pebble Time Round, and Pebble 2 there should be no problem. For previous generation devices like Pebble and Pebble Steel it means that their users should upgrade their firmware to the latest firmware available for their devices using the new apps. This change allows better compatibility and new features to be developed by 3rd parties. --- # Sending and Receiving Data Source: https://developer.repebble.com/guides/communication/sending-and-receiving-data/ Before using [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"), a Pebble C app must set up the buffers used for the inbox and outbox. These are used to store received messages that have not yet been processed, and sent messages that have not yet been transmitted. In addition, callbacks may be registered to allow an app to respond to any success or failure events that occur when dealing with messages. Doing all of this is discussed in this guide. ## Message Structure Every message sent or received using the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") API is stored in a [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") structure, which is essentially a list of [`Tuple`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple "Tuple") objects. Each [`Tuple`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple "Tuple") contains a key used to 'label' the value associated with that key. When a message is sent, a [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") is filled with a [`Tuple`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple "Tuple") for each item of outgoing data. Conversely, when a message is received the [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") provided by the callback is examined for the presence of each key. If a key is present, the value associated with it can be read. ## Data Types The [`Tuple.value`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple "Tuple") union allows multiple data types to be stored in and read from each received message. These are detailed below: | Name | Type | Size in Bytes | Signed? | | --- | --- | --- | --- | | uint8 | `uint8_t` | 1 | No | | uint16 | `uint16_t` | 2 | No | | uint32 | `uint32_t` | 4 | No | | int8 | `int8_t` | 1 | Yes | | int16 | `int16_t` | 2 | Yes | | int32 | `int32_t` | 4 | Yes | | cstring | `char[]` | Variable length array | N/A | | data | `uint8_t[]` | Variable length array | N/A | ## Buffer Sizes The size of each of the outbox and inbox buffers must be set chosen such that the largest message that the app will ever send or receive will fit. Incoming messages that exceed the size of the inbox buffer, and outgoing messages that exceed that size of the outbox buffer will be dropped. These sizes are specified when the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") system is 'opened', allowing communication to occur: ``` // Largest expected inbox and outbox message sizesconst uint32\_t inbox\_size = 64;const uint32\_t outbox\_size = 256;// Open AppMessageapp\_message\_open(inbox\_size, outbox\_size); ``` Each of these buffers is allocated at this moment, and comes out of the app's memory budget, so the sizes of the inbox and outbox should be conservative. Calculate the size of the buffer you require by summing the sizes of all the keys and values in the larges message the app will handle. For example, a message containing three integer keys and values will work with a 32 byte buffer size. ## Choosing Keys For each message sent and received, the contents are accessible using keys-value pairs in a [`Tuple`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple "Tuple"). This allows each piece of data in the message to be uniquely identifiable using its key, and also allows many different data types to be stored inside a single message. Each possible piece of data that may be transmitted should be assigned a unique key value, used to read the associated value when it is found in a received message. An example for a weather app is shown below:: - Temperature - WindSpeed - WindDirection - RequestData - LocationName These values will be made available in any file that includes `pebble.h` prefixed with `MESSAGE_KEY_`, such as `MESSAGE_KEY_Temperature` and `MESSAGE_KEY_WindSpeed`. Examples of how these key values would be used in the phone-side app are shown in [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md), [_PebbleKit iOS_](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md), and [_PebbleKit Android_](https://developer.repebble.com/guides/communication/using-pebblekit-android.md). ## Using Callbacks Like many other aspects of the Pebble C API, the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") system makes use of developer-defined callbacks to allow an app to gracefully handle all events that may occur, such as successfully sent or received messages as well as any errors that may occur. These callback types are discussed below. Each is used by first creating a function that matches the signature of the callback type, and then registering it with the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") system to be called when that event type occurs. Good use of callbacks to drive the app's UI will result in an improved user experience, especially when errors occur that the user can be guided in fixing. ### Inbox Received The [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") callback is called when a new message has been received from the connected phone. This is the moment when the contents can be read and used to drive what the app does next, using the provided [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") to read the message. An example is shown below under [_Reading an Incoming Message_](#reading-an-incoming-message): ``` static void inbox\_received\_callback(DictionaryIterator \*iter, void \*context) {// A new message has been successfully received} ``` Register this callback so that it is called at the appropriate time: ``` // Register to be notified about inbox received eventsapp\_message\_register\_inbox\_received(inbox\_received\_callback); ``` ### Inbox Dropped The [`AppMessageInboxDropped`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxDropped "AppMessageInboxDropped") callback is called when a message was received, but it was dropped. A common cause of this is that the message was too big for the inbox. The reason for failure can be determined using the [`AppMessageResult`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult "AppMessageResult") provided by the callback: ``` static void inbox\_dropped\_callback(AppMessageResult reason, void \*context) {// A message was received, but had to be droppedAPP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Message dropped. Reason: %d", (int)reason);} ``` Register this callback so that it is called at the appropriate time: ``` // Register to be notified about inbox dropped eventsapp\_message\_register\_inbox\_dropped(inbox\_dropped\_callback); ``` ### Outbox Sent The [`AppMessageOutboxSent`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent "AppMessageOutboxSent") callback is called when a message sent from Pebble has been successfully delivered to the connected phone. The provided [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") can be optionally used to inspect the contents of the message just sent. > When sending multiple messages in a short space of time, it is **strongly** recommended to make use of this callback to wait until the previous message has been sent before sending the next. ``` static void outbox\_sent\_callback(DictionaryIterator \*iter, void \*context) {// The message just sent has been successfully delivered} ``` Register this callback so that it is called at the appropriate time: ``` // Register to be notified about outbox sent eventsapp\_message\_register\_outbox\_sent(outbox\_sent\_callback); ``` ### Outbox Failed The [`AppMessageOutboxFailed`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxFailed "AppMessageOutboxFailed") callback is called when a message just sent failed to be successfully delivered to the connected phone. The reason can be determined by reading the value of the provided [`AppMessageResult`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult "AppMessageResult"), and the contents of the failed message inspected with the provided [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator"). Use of this callback is strongly encouraged, since it allows an app to detect a failed message and either retry its transmission, or inform the user of the failure so that they can attempt their action again. ``` static void outbox\_failed\_callback(DictionaryIterator \*iter,AppMessageResult reason, void \*context) {// The message just sent failed to be deliveredAPP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Message send failed. Reason: %d", (int)reason);} ``` Register this callback so that it is called at the appropriate time: ``` // Register to be notified about outbox failed eventsapp\_message\_register\_outbox\_failed(outbox\_failed\_callback); ``` ## Constructing an Outgoing Message A message is constructed and sent from the C app via [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") using a [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") object and the [`Dictionary`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md "Dictionary") APIs. Ensure that [`app_message_open()`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_open "app\_message\_open") has been called before sending or receiving any messages. The first step is to begin an outgoing message by preparing a [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") pointer, used to keep track of the state of the dictionary being constructed: ``` // Declare the dictionary's iteratorDictionaryIterator \*out\_iter;// Prepare the outbox buffer for this messageAppMessageResult result = app\_message\_outbox\_begin(&out\_iter); ``` The [`AppMessageResult`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult "AppMessageResult") should be checked to make sure the outbox was successfully prepared: ``` if(result == APP\_MSG\_OK) {// Construct the message} else {// The outbox cannot be used right nowAPP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Error preparing the outbox: %d", (int)result);} ``` If the result is [`APP_MSG_OK`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_OK "APP\_MSG\_OK"), the message construction can continue. Data is now written to the dictionary according to data type using the [`Dictionary`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md "Dictionary") APIs. An example from the hypothetical weather app is shown below: ``` if(result == APP\_MSG\_OK) {// A dummy valueint value = 0;// Add an item to ask for weather datadict\_write\_int(out\_iter, MESSAGE\_KEY\_RequestData, &value, sizeof(int), true);} ``` After all desired data has been written to the dictionary, the message may be sent: ``` // Send this messageresult = app\_message\_outbox\_send();// Check the resultif(result != APP\_MSG\_OK) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Error sending the outbox: %d", (int)result);} ``` **Important** Any app that wishes to send data from the watch to the phone via PebbleKit JS must wait until the `ready` event has occured, indicating that the phone has loaded the JavaScript for the app and it is ready to receive data. See [_Advanced Communication_](https://developer.repebble.com/guides/communication/advanced-communication.md#waiting-for-pebblekit-js) for more information. Once the message send operation has been completed, either the [`AppMessageOutboxSent`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent "AppMessageOutboxSent") or [`AppMessageOutboxFailed`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxFailed "AppMessageOutboxFailed") callbacks will be called (if they have been registered), depending on either a success or failure outcome. ### Example Outgoing Message Construction A complete example of assembling an outgoing message is shown below: ``` // Declare the dictionary's iteratorDictionaryIterator \*out\_iter;// Prepare the outbox buffer for this messageAppMessageResult result = app\_message\_outbox\_begin(&out\_iter);if(result == APP\_MSG\_OK) {// Add an item to ask for weather dataint value = 0;dict\_write\_int(out\_iter, MESSAGE\_KEY\_RequestData, &value, sizeof(int), true);// Send this messageresult = app\_message\_outbox\_send();if(result != APP\_MSG\_OK) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Error sending the outbox: %d", (int)result);}} else {// The outbox cannot be used right nowAPP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Error preparing the outbox: %d", (int)result);} ``` ## Reading an Incoming Message When a message is received from the connected phone the [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") callback is called, and the message's contents can be read using the provided [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator"). This should be done by looking for the presence of each expectd `Tuple` key value, and using the associated value as required. Most apps will deal with integer values or strings to pass signals or some human-readable information respectively. These common use cases are discussed below. ### Reading an Integer **From JS** ``` var dict= {'Temperature': 29}; ``` **In C** ``` static void inbox\_received\_callback(DictionaryIterator \*iter, void \*context) {// A new message has been successfully received// Does this message contain a temperature value?Tuple \*temperature\_tuple = dict\_find(iter, MESSAGE\_KEY\_Temperature);if(temperature\_tuple) {// This value was stored as JS Number, which is stored here as int32\_tint32\_t temperature = temperature\_tuple-\>value-\>int32;}} ``` ### Reading a String A common use of transmitted strings is to display them in a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). Since the displayed text is required to be long-lived, a `static` `char` buffer can be used when the data is received: **From JS** ``` var dict = {'LocationName': 'London, UK'}; ``` **In C** ``` static void inbox\_received\_callback(DictionaryIterator \*iter, void \*context) {// Is the location name inside this message?Tuple \*location\_tuple = dict\_find(iter, MESSAGE\_KEY\_LocationName);if(location\_tuple) {// This value was stored as JS String, which is stored here as a char stringchar \*location\_name = location\_tuple-\>value-\>cstring;// Use a static buffer to store the string for displaystatic char s\_buffer[MAX\_LENGTH];snprintf(s\_buffer, sizeof(s\_buffer), "Location: %s", location\_name);// Display in the TextLayertext\_layer\_set\_text(s\_text\_layer, s\_buffer);}} ``` ### Reading Binary Data Apps that deal in packed binary data can send this data and pack/unpack as required on either side: **From JS** ``` var dict = {'Data': [1, 2, 4, 8, 16, 32, 64]}; ``` **In C** ``` static void inbox\_received\_callback(DictionaryIterator \*iter, void \*context) {// Expected length of the binary dataconst int length = 32;// Does this message contain the data tuple?Tuple \*data\_tuple = dict\_find(iter, MESSAGE\_KEY\_Data);if(data\_tuple) {// Read the binary data valueuint8\_t \*data = data\_tuple-\>value-\>data;// Inspect the first byte, for exampleuint8\_t byte\_zero = data[0];// Store into an app-defined buffermemcpy(s\_buffer, data, length);} ``` --- # Sports API Source: https://developer.repebble.com/guides/communication/using-the-sports-api/ Every Pebble watch has two built-in system watchapps called the Sports app, and the Golf app. These apps are hidden from the launcher until launched via PebbleKit Android or PebbleKit iOS. Both are designed to be generic apps that display sports-related data in common formats. The goal is to allow fitness and golf mobile apps to integrate with Pebble to show the wearer data about their activity without needing to create and maintain an additional app for Pebble. An example of a popular app that uses this approach is the [Runkeeper](http://apps.repebble.com/application/52e05bd5d8561de307000039) app. The Sports and Golf apps are launched, closed, and controlled by PebbleKit in an Android or iOS app, shown by example in each section below. In both cases, the data fields that are available to be populated are different, but data is pushed in the same way. ## Available Data Fields ### Sports #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/sports~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/sports~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/sports~chalk.png) The sports app displays activity duration and distance which can apply to a wide range of sports, such as cycling or running. A configurable third field is also available that displays pace or speed, depending on the app's preference. The Sports API also allows the app to be configured to display the labels of each field in metric (the default) or imperial units. The action bar is used to prompt the user to use the Select button to pause and resume their activity session. The companion app is responsible for listening for these events and implementing the pause/resume operation as appropriate. ### Golf #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/golf~aplite.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/golf~basalt.png) ![](https://developer.repebble.com/assets/images/guides/design-and-interaction/golf~chalk.png) The Golf app is specialized to displaying data relevant to golf games, including par and hole numbers, as well as front, mid, and rear yardage. Similar to the Sports app, the action bar is used to allow appropriate feedback to the companion app. In this case the actions are an 'up', 'ball' and 'down' events which the companion should handle as appropriate. ## With PebbleKit Android Once an Android app has set up [_PebbleKit Android_](https://developer.repebble.com/guides/communication/using-pebblekit-android.md), the Sports and Golf apps can be launched and customized as appropriate. ### Launching Sports and Golf To launch one of the Sports API apps, simply call `startAppOnPebble()` and supply the UUID from the `Constants` class: ``` // Launch the Sports appPebbleKit.startAppOnPebble(getApplicationContext(), Constants.SPORTS\_UUID); ``` ### Customizing Sports To choose which unit type is used, construct and send a `PebbleDictionary` containing the desired value from the `Constants` class. Either `SPORTS_UNITS_IMPERIAL` or `SPORTS_UNITS_METRIC` can be used: ``` PebbleDictionary dict = new PebbleDictionary();// Display imperial unitsdict.addUint8(Constants.SPORTS\_UNITS\_KEY, Constants.SPORTS\_UNITS\_IMPERIAL);PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS\_UUID, dict); ``` To select between 'pace' or 'speed' as the label for the third field, construct and send a `PebbleDictionary` similar to the example above. This can be done in the same message as unit selection: ``` PebbleDictionary dict = new PebbleDictionary();// Display speed instead of pacedict.addUint8(Constants.SPORTS\_LABEL\_KEY, Constants.SPORTS\_DATA\_SPEED);PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS\_UUID, dict); ``` > Note: The Golf app does not feature any customizable fields. ### Displaying Data Data about the current activity can be sent to either of the Sports API apps using a `PebbleDictionary`. For example, to show a value for duration and distance in the Sports app: ``` PebbleDictionary dict = new PebbleDictionary();// Show a value for duration and distancedict.addString(Constants.SPORTS\_TIME\_KEY, "12:52");dict.addString(Constants.SPORTS\_DISTANCE\_KEY, "23.8");PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS\_UUID, dict); ``` Read the [`Constants`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md) documentation to learn about all the available parameters that can be used for customization. ### Handling Button Events When a button event is generated from one of the Sports API apps, a message is sent to the Android companion app, which can be processed using a `PebbleDataReceiver`. For example, to listen for a change in the state of the Sports app, search for `Constants.SPORTS_STATE_KEY` in the received `PebbleDictionary`. The user is notified in the example below through the use of an Android [`Toast`](http://developer.android.com/guide/topics/ui/notifiers/toasts.html): ``` // Create a receiver for when the Sports app state changesPebbleDataReceiver reciever = new PebbleKit.PebbleDataReceiver(Constants.SPORTS\_UUID) {@Overridepublic void receiveData(Context context, int id, PebbleDictionary data) {// Always ACKnowledge the last message to prevent timeoutsPebbleKit.sendAckToPebble(getApplicationContext(), id);// Get action and display as ToastLong value = data.getUnsignedIntegerAsLong(Constants.SPORTS\_STATE\_KEY);if(value != null) {int state = value.intValue();String text = (state == Constants.SPORTS\_STATE\_PAUSED)? "Resumed!" : "Paused!";Toast.makeText(getApplicationContext(), text, Toast.LENGTH\_SHORT).show();}}};// Register the receiverPebbleKit.registerReceivedDataHandler(getApplicationContext(), receiver); ``` ## With PebbleKit iOS Once an iOS app has set up [_PebbleKit iOS_](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md), the Sports and Golf apps can be launched and customized as appropriate. The companion app should set itself as a delegate of `PBPebbleCentralDelegate`, and assign a `PBWatch` property once `watchDidConnect:` has fired. This `PBWatch` object will then be used to manipulate the Sports API apps. Read _Becoming a Delegate_ in the [_PebbleKit iOS_](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) guide to see how this is done. ### Launching Sports and Golf To launch one of the Sports API apps, simply call `sportsAppLaunch:` or `golfAppLaunch:` as appropriate: ``` [self.watch sportsAppLaunch:^(PBWatch \* \_Nonnull watch,NSError \* \_Nullable error) {NSLog(@"Sports app was launched");}]; ``` ### Customizing Sports To choose which unit type is used, call `sportsAppSetMetric:` with the desired `isMetric` `BOOL`: ``` BOOL isMetric = YES;[self.watch sportsAppSetMetric:isMetric onSent:^(PBWatch \* \_Nonnull watch,NSError \* \_Nonnull error) {if (!error) {NSLog(@"Successfully sent message.");} else {NSLog(@"Error sending message: %@", error);}}]; ``` To select between 'pace' or 'speed' as the label for the third field, call `sportsAppSetLabel:` with the desired `isPace` `BOOL`: ``` BOOL isPace = YES;[self.watch sportsAppSetLabel:isPace onSent:^(PBWatch \* \_Nonnull watch,NSError \* \_Nullable error) {if (!error) {NSLog(@"Successfully sent message.");} else {NSLog(@"Error sending message: %@", error);}}]; ``` > Note: The Golf app does not feature any customizable fields. ### Displaying Data Data about the current activity can be sent to either the Sports or Golf app using `sportsAppUpdate:` or `golfAppUpdate:`. For example, to show a value for duration and distance in the Sports app: ``` // Construct a dictionary of dataNSDictionary \*update = @{ PBSportsTimeKey: @"12:34",PBSportsDistanceKey: @"6.23" };// Send the data to the Sports app[self.watch sportsAppUpdate:update onSent:^(PBWatch \* \_Nonnull watch,NSError \* \_Nullable error) {if (!error) {NSLog(@"Successfully sent message.");} else {NSLog(@"Error sending message: %@", error);}}]; ``` Read the [`PBWatch`](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md) documentation to learn about all the available methods and values for customization. ### Handling Button Events When a button event is generated from one of the Sports API apps, a message is sent to the Android companion app, which can be processed using `sportsAppAddReceiveUpdateHandler` and supplying a block to be run when a message is received. For example, to listen for change in state of the Sports app, check the value of the provided `SportsAppActivityState`: ``` // Register to get state updates from the Sports app[self.watch sportsAppAddReceiveUpdateHandler:^BOOL(PBWatch \*watch,SportsAppActivityState state) {// Display the new state of the watchappswitch (state) {case SportsAppActivityStateRunning:NSLog(@"Watchapp now running.");break;case SportsAppActivityStatePaused:NSLog(@"Watchapp now paused.");break;default: break;}// Finallyreturn YES;}]; ``` --- # Accelerometer Source: https://developer.repebble.com/guides/events-and-services/accelerometer/ The acceleromter sensor is included in every Pebble watch, and allows collection of acceleration and orientation data in watchapps and watchfaces. Data is available in two ways, each suitable to different types of watchapp: - Taps events - Fires an event whenever a significant tap or shake of the watch occurs. Useful to 'shake to view' features. - Data batches - Allows collection of data in batches at specific intervals. Useful for general accelerometer data colleciton. As a significant source of regular callbacks, the accelerometer should be used as sparingly as possible to allow the watch to sleep and conserve power. For example, receiving data in batches once per second is more power efficient than receiving a single sample 25 times per second. ## About the Pebble Accelerometer The Pebble accelerometer is oriented according to the diagram below, showing the direction of each of the x, y, and z axes. ![accel-axes](https://developer.repebble.com/assets/images/guides/pebble-apps/sensors/accel.png) In the API, each axis value contained in an [`AccelData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelData "AccelData") sample is measured in milli-Gs. The accelerometer is calibrated to measure a maximum acceleration of ±4G. Therefore, the range of possible values for each axis is -4000 to +4000. The [`AccelData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelData "AccelData") sample object also contains a `did_vibrate` field, set to `true` if the vibration motor was active during the sample collection. This could possibly contaminate those samples due to onboard vibration, so they should be discarded. Lastly, the `timestamp` field allows tracking of obtained accelerometer data over time. ## Using Taps Adding a subscription to tap events allows a developer to react to any time the watch is tapped or experiences a shake along one of three axes. Tap events are received by registering an [`AccelTapHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelTapHandler "AccelTapHandler") function, such as the one below: ``` static void accel\_tap\_handler(AccelAxisType axis, int32\_t direction) {// A tap event occured} ``` The `axis` parameter describes which axis the tap was detected along. The `direction` parameter is set to `1` for the positive direction, and `-1` for the negative direction. A subscription can be added or removed at any time. While subscribed, `accel_tap_handler` will be called whenever a tap event is fired by the accelerometer. Adding a subscription is simple: ``` // Subscribe to tap eventsaccel\_tap\_service\_subscribe(accel\_tap\_handler); ``` ``` // Unsubscribe from tap eventsaccel\_tap\_service\_unsubscribe(); ``` ## Using Data Batches Accelerometer data can be received in batches at a chosen sampling rate by subscribing to the Accelerometer Data Service at any time: ``` uint32\_t num\_samples = 3;// Number of samples per batch/callback// Subscribe to batched data eventsaccel\_data\_service\_subscribe(num\_samples, accel\_data\_handler); ``` The [`AccelDataHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelDataHandler "AccelDataHandler") function (called `accel_data_handler` in the example above) is called when a new batch of data is ready for consumption by the watchapp. The rate at which these occur is dictated by two things: - The sampling rate - The number of samples the accelerometer device measures per second. One value chosen from the [`AccelSamplingRate`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelSamplingRate "AccelSamplingRate") `enum`. - The number of samples per batch. Some simple math will determine how often the callback will occur. For example, at the [`ACCEL_SAMPLING_50HZ`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_SAMPLING_50HZ "ACCEL\_SAMPLING\_50HZ") sampling rate, and specifying 10 samples per batch will result in five calls per second. When an event occurs, the acceleromater data can be read from the [`AccelData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelData "AccelData") pointer provided in the callback. An example reading the first set of values is shown below: ``` static void accel\_data\_handler(AccelData \*data, uint32\_t num\_samples) {// Read sample 0's x, y, and z valuesint16\_t x = data[0].x;int16\_t y = data[0].y;int16\_t z = data[0].z;// Determine if the sample occured during vibration, and when it occuredbool did\_vibrate = data[0].did\_vibrate;uint64\_t timestamp = data[0].timestamp;if(!did\_vibrate) {// Print it outAPP\_LOG(APP\_LOG\_LEVEL\_INFO, "t: %llu, x: %d, y: %d, z: %d",timestamp, x, y, z);} else {// Discard with a warningAPP\_LOG(APP\_LOG\_LEVEL\_WARNING, "Vibration occured during collection");}} ``` The code above will output the first sample in each batch to app logs, which will look similar to the following: ``` [15:33:18] -data-service.c:21> t: 1449012797098, x: -111, y: -280, z: -1153 [15:33:18] -data-service.c:21> t: 1449012797305, x: -77, y: 40, z: -1014 [15:33:18] -data-service.c:21> t: 1449012797507, x: -60, y: 4, z: -1080 [15:33:19] -data-service.c:21> t: 1449012797710, x: -119, y: -55, z: -921 [15:33:19] -data-service.c:21> t: 1449012797914, x: 628, y: 64, z: -506 ``` --- # Background Worker Source: https://developer.repebble.com/guides/events-and-services/background-worker/ In addition to the main foreground task that every Pebble app implements, a second background worker task can also be created. This worker is capable of running even when the foreground task is closed, and is useful for tasks that must continue for long periods of time. For example, apps that log sensor data. There are several important points to note about the capabilities of this worker when compared to those of the foreground task: - The worker is constrained to 10.5 kB of memory. - Some APIs are not available to the worker. See the [_Available APIs_](#available-apis) section below for more information. - There can only be one background worker active at a time. In the event that a second one attempts to launch from another watchapp, the user will be asked to choose whether the new worker can replace the existing one. - The user can determine which app's worker is running by checking the 'Background App' section of the Settings menu. Workers can also be launched from there. - The worker can launch the foreground app using [`worker_launch_app()`](https://developer.repebble.com/docs/c/Worker.md#worker_launch_app "worker\_launch\_app"). This means that the foreground app should be prepared to be launched at any time that the worker is running. > Note: This API should not be used to build background timers; use the [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup") API instead. ## Adding a Worker The background worker's behavior is determined by code written in a separate C file to the foreground app, created in the `/worker_src` project directory. This project structure can also be generated using the [`pebble` tool](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) with the `--worker` flag as shown below: ``` $ pebble new-project --worker project_name ``` The worker C file itself has a basic structure similar to a regular Pebble app, but with a couple of minor changes, as shown below: ``` #include \static void prv\_init() {// Initialize the worker here}static void prv\_deinit() {// Deinitialize the worker here}int main(void) {prv\_init();worker\_event\_loop();prv\_deinit();} ``` ## Launching the Worker To launch the worker from the foreground app, use [`app_worker_launch()`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_launch "app\_worker\_launch"): ``` // Launch the background workerAppWorkerResult result = app\_worker\_launch(); ``` The [`AppWorkerResult`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerResult "AppWorkerResult") returned will indicate any errors encountered as a result of attempting to launch the worker. Possible result values include: | Result | Value | Description | | --- | --- | --- | | [`APP_WORKER_RESULT_SUCCESS`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_SUCCESS "APP\_WORKER\_RESULT\_SUCCESS") | `0` | The worker launch was successful, but may not start running immediately. Use [`app_worker_is_running()`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_is_running "app\_worker\_is\_running") to determine when the worker has started running. | | [`APP_WORKER_RESULT_NO_WORKER`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_NO_WORKER "APP\_WORKER\_RESULT\_NO\_WORKER") | `1` | No worker found for the current app. | | [`APP_WORKER_RESULT_ALREADY_RUNNING`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_ALREADY_RUNNING "APP\_WORKER\_RESULT\_ALREADY\_RUNNING") | `4` | The worker is already running. | | [`APP_WORKER_RESULT_ASKING_CONFIRMATION`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_ASKING_CONFIRMATION "APP\_WORKER\_RESULT\_ASKING\_CONFIRMATION") | `5` | The user will be asked for confirmation. To determine whether the worker was given permission to launch, use [`app_worker_is_running()`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_is_running "app\_worker\_is\_running") for a short period after receiving this result. | ## Communicating Between Tasks There are three methods of passing data between the foreground and background worker tasks: - Save the data using the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API, then read it in the other task. - Send the data to a companion phone app using the [`DataLogging`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md "DataLogging") API. Details on how to do this are available in [_Datalogging_](https://developer.repebble.com/guides/communication/datalogging.md). - Pass the data directly while the other task is running, using an [`AppWorkerMessage`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerMessage "AppWorkerMessage"). These messages can be sent bi-directionally by creating an `AppWorkerMessageHandler` in each task. The handler will fire in both the foreground and the background tasks, so you must identify the source of the message using the `type` parameter. ## Managing the Worker The current running state of the background worker can be determined using the [`app_worker_is_running()`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_is_running "app\_worker\_is\_running") function: ``` // Check to see if the worker is currently activebool running = app\_worker\_is\_running(); ``` The user can tell whether the worker is running by checking the system 'Background App' settings. Any installed workers with be listed there. The worker can be stopped using [`app_worker_kill()`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_kill "app\_worker\_kill"): ``` // Stop the background workerAppWorkerResult result = app\_worker\_kill(); ``` Possible `result` values when attempting to kill the worker are as follows: | Result | Value | Description | | --- | --- | --- | | [`APP_WORKER_RESULT_SUCCESS`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_SUCCESS "APP\_WORKER\_RESULT\_SUCCESS") | `0` | The worker launch was killed successfully. | | [`APP_WORKER_RESULT_DIFFERENT_APP`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_DIFFERENT_APP "APP\_WORKER\_RESULT\_DIFFERENT\_APP") | `2` | A worker from a different app is running, and cannot be killed by this app. | | [`APP_WORKER_RESULT_NOT_RUNNING`](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_NOT_RUNNING "APP\_WORKER\_RESULT\_NOT\_RUNNING") | `3` | The worker is not currently running. | ## Available APIs Background workers do not have access to the UI APIs. They also cannot use the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") API or load resources. Most other APIs are available including (but not limited to) [`AccelerometerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md "AccelerometerService"), [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService"), [`DataLogging`](https://developer.repebble.com/docs/c/Foundation/DataLogging.md "DataLogging"), [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService"), [`ConnectionService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md "ConnectionService"), [`BatteryStateService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md "BatteryStateService"), [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") and [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage"). The compiler will throw an error if the developer attempts to use an API unsupported by the worker. For a definitive list of available APIs, check `pebble_worker.h` in the SDK bundle for the presence of the desired API. --- # Backlight Source: https://developer.repebble.com/guides/events-and-services/light/ The Light API gives an app limited control over the watch backlight. An app can ask the system to flash the backlight as if the user had performed a wrist gesture, or force it on for as long as it needs. On hardware with an RGB backlight, the same API can also tint that backlight any color the user can render in their UI. All of these calls operate on top of the user's normal backlight settings (auto on-shake, brightness, etc.), so apps don't need to worry about overriding the user's preferences permanently. ## Triggering the Backlight The simplest and most common use of the Light API is to trigger the backlight exactly as a wrist flick would. The light comes on, stays on for the system auto-off interval, and then fades out: ``` // Turn the backlight on for the standard auto-off intervallight\_enable\_interaction(); ``` This is the recommended entry point for most apps. It respects the user's backlight setting (it does nothing if the user has the backlight disabled) and does not require a follow-up call to turn the light back off. ## Forcing the Backlight On If an app needs the backlight on for an extended interaction - for example, during a flashlight feature or while the user is doing something with both hands - it can pin it on: ``` // Force the backlight onlight\_enable(true); ``` The backlight stays on until the app explicitly returns control to the automatic system: ``` // Hand control back to the system's auto-off behaviorlight\_enable(false); ``` > Holding the backlight on draws significantly more power than letting it auto-off. The system also dims the backlight automatically when the battery is low to protect the user's remaining runtime, so a forced-on backlight is not a guarantee of full brightness. To check whether the backlight is currently on, use: ``` if (light\_is\_on()) {// The backlight is currently lit} ``` ## Reacting to Backlight Changes The [`BacklightService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md "BacklightService") tells an app when the backlight turns on or off, whether the user flicked their wrist, pressed a button or the auto-off timer ran out. A watchface can use it to switch to a brighter color scheme while the light is on, or to pause an animation when the display is no longer being looked at: ``` static void backlight\_handler(bool on) {// on is true when the backlight has just turned on,// false when it has turned fully offlayer\_mark\_dirty(s\_canvas\_layer);}// Subscribebacklight\_service\_subscribe(backlight\_handler);// Later, stop receiving eventsbacklight\_service\_unsubscribe(); ``` The handler is called once per transition. Any state other than fully off, including the fade-out, counts as on. Use [`light_is_on()`](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_is_on "light\_is\_on") to read the current state without subscribing. [`backlight_service_subscribe()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#backlight_service_subscribe "backlight\_service\_subscribe") is available on Pebble 2 Duo (flint), Pebble Time 2 (emery) and Pebble Round 2 (gabbro). On aplite, basalt, chalk and diorite the SDK compiles it to a no-op, so the handler is never called. ## Tinting the Backlight (RGB Hardware Only) On hardware with an RGB backlight, the backlight color can be set to any [`GColor`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColor "GColor") value. The color persists for as long as the app is in the foreground, then resets to the user's default when the app exits or is preempted (for example, by an incoming notification modal). ``` // Tint the backlight redlight\_set\_color(GColorRed); ``` `light_set_color()` takes a [`GColor`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColor "GColor"), which on current hardware quantizes to the 64-color palette the UI uses. For more control over the exact tint - for example, to match a brand color or blend between two palette entries - use `light_set_color_rgb888()`, which takes a 24-bit RGB value packed into a `uint32_t` as `0x00RRGGBB`: ``` // Tint the backlight with an exact RGB color (teal)light\_set\_color\_rgb888(0x0088CC); ``` Once set, the tint persists for the rest of the app's foreground session - every subsequent backlight trigger will use that color until the app changes it again or exits. To return to the user's chosen default mid-session, call: ``` // Restore the user's default backlight colorlight\_set\_system\_color(); ``` This is important if the app uses different colors for different states (for example, red for an alert and the user default the rest of the time): without an explicit `light_set_system_color()` call the alert color will stick around for every subsequent backlight flash. The system does reset the color automatically when the app exits or is preempted, so there's no need to call `light_set_system_color()` purely on the way out. On hardware without an RGB backlight these two calls are no-ops and can be left in unconditionally; there's no need to wrap them in a preprocessor guard just to compile against other platforms. For cases where a whole block of color-specific logic should be excluded on non-RGB platforms, the `PBL_RGB_BACKLIGHT` compile-time define is present on platforms that have an RGB backlight: ``` #if defined(PBL\_RGB\_BACKLIGHT)// Use a different tint per app statelight\_set\_color(is\_alert ? GColorRed : GColorWhite);#endiflight\_enable\_interaction(); ``` Tinting only changes the _color_ of the backlight; the backlight still needs to be on for the user to see it. Combine `light_set_color()` with `light_enable_interaction()` or `light_enable(true)` so the user actually sees the new color: ``` // Flash the backlight red for the system auto-off intervallight\_set\_color(GColorRed);light\_enable\_interaction(); ``` ## Battery Considerations The backlight is one of the most power-hungry components on the watch. Triggering it through `light_enable_interaction()` for short user interactions is generally fine, but holding it on for long periods with `light_enable(true)` will noticeably reduce battery life. Use the forced-on mode only when the feature genuinely requires it, and make sure to release control with `light_enable(false)` as soon as the user is done. --- # Buttons Source: https://developer.repebble.com/guides/events-and-services/buttons/ Button [`Clicks`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md "Clicks") are the primary input method on Pebble. All Pebble watches come with the same buttons available, shown in the diagram below for Pebble Time: ![button-layout](https://developer.repebble.com/assets/images/guides/sensors-and-input/button-layout.png) These buttons are used in a logical fashion throughout the system: - Back - Navigates back one [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") until the watchface is reached. - Up - Navigates to the previous item in a list, or opens the past timeline when pressed from the watchface. - Select - Opens the app launcher from the watchface, accepts a selected option or list item, or launches the next [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). - Down - Navigates to the next item in a list, or opens the future timeline when pressed from the watchface. Developers are highly encouraged to follow these patterns when using button clicks in their watchapps, since users will already have an idea of what each button will do to a reasonable degree, thus avoiding the need for lengthy usage instructions for each app. Watchapps that wish to use each button for a specific action should use the [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") or [`ActionMenu`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md "ActionMenu") to give hints about what each button will do. > Note: Watchfaces do not receive button clicks. A [`ClickConfigProvider`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider "ClickConfigProvider") set on a watchface [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is still called, but the system handles the button events itself: by default Up opens Health and Down opens the timeline (both can be reassigned in the watch's Quick Launch settings), Select opens the launcher and Back is used for Quick Launch, so the subscribed handlers never run. Build a watchapp instead, or read [_App Exit Reason_](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md) and the launch reasons in [_One Click Actions_](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) for ways to open an app from the watchface. ## Listening for Button Clicks Button clicks are received via a subscription to one of the types of button click events listed below. Each [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") that wishes to receive button click events must provide a [`ClickConfigProvider`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider "ClickConfigProvider") that performs these subscriptions. The first step is to create the [`ClickConfigProvider`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider "ClickConfigProvider") function: ``` static void click\_config\_provider(void \*context) {// Subcribe to button click events here} ``` The second step is to register the [`ClickConfigProvider`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider "ClickConfigProvider") with the current [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"), typically after [`window_create()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_create "window\_create"): ``` // Use this provider to add button click subscriptionswindow\_set\_click\_config\_provider(window, click\_config\_provider); ``` The final step is to write a [`ClickHandler`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler "ClickHandler") function for each different type of event subscription required by the watchapp. An example for a single click event is shown below: ``` static void select\_click\_handler(ClickRecognizerRef recognizer, void \*context) {// A single click has just occured} ``` ## Types of Click Events There are five types of button click events that apps subscribe to, enabling virtually any combination of up/down/click events to be utilized in a watchapp. The usage of each of these is explained below: ### Single Clicks Most apps will use this type of click event, which occurs whenever the button specified is pressed and then immediately released. Use [`window_single_click_subscribe()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe "window\_single\_click\_subscribe") from a [`ClickConfigProvider`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider "ClickConfigProvider") function, supplying the [`ButtonId`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId "ButtonId") value for the chosen button and the name of the [`ClickHandler`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler "ClickHandler") that will receive the events: ``` static void click\_config\_provider(void \*context) {ButtonId id = BUTTON\_ID\_SELECT;// The Select buttonwindow\_single\_click\_subscribe(id, select\_click\_handler);} ``` ### Single Repeating Clicks Similar to the single click event, the single repeating click event allows repeating events to be received at a specific interval if the chosen button is held down for a longer period of time. This makes the task of scrolling through many list items or incrementing a value significantly easier for the user, and uses fewer button clicks. ``` static void click\_config\_provider(void \*context) {ButtonId id = BUTTON\_ID\_DOWN;// The Down buttonuint16\_t repeat\_interval\_ms = 200;// Fire every 200 ms while held downwindow\_single\_repeating\_click\_subscribe(id, repeat\_interval\_ms,down\_repeating\_click\_handler);} ``` After an initial press (but not release) of the button `id` subscribed to, the callback will be called repeatedly with an interval of `repeat_interval_ms` until it is then released. Developers can determine if the button is still held down after the first callback by using [`click_recognizer_is_repeating()`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_recognizer_is_repeating "click\_recognizer\_is\_repeating"), as well as get the number of callbacks counted so far with [`click_number_of_clicks_counted()`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_number_of_clicks_counted "click\_number\_of\_clicks\_counted"): ``` static void down\_repeating\_click\_handler(ClickRecognizerRef recognizer,void \*context) {// Is the button still held down?bool is\_repeating = click\_recognizer\_is\_repeating(recognizer);// How many callbacks have been recorded so far?uint8\_t click\_count = click\_number\_of\_clicks\_counted(recognizer);} ``` > Single click and single repeating click subscriptions conflict, and cannot be registered for the same button. ### Multiple Clicks A multi click event will call the [`ClickHandler`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler "ClickHandler") after a specified number of single clicks has been recorded. A good example of usage is to detect a double or triple click gesture: ``` static void click\_config\_provider(void \*context) {ButtonId id = BUTTON\_ID\_SELECT;// The Select buttonuint8\_t min\_clicks = 2;// Fire after at least two clicksuint8\_t max\_clicks = 3;// Don't fire after three clicksuint16\_t timeout = 300;// Wait 300ms before firingbool last\_click\_only = true;// Fire only after the last clickwindow\_multi\_click\_subscribe(id, min\_clicks, max\_clicks, timeout,last\_click\_only, multi\_select\_click\_handler);} ``` Similar to the single repeating click event, the [`ClickRecognizerRef`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef "ClickRecognizerRef") can be used to determine how many clicks triggered this multi click event using [`click_number_of_clicks_counted()`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_number_of_clicks_counted "click\_number\_of\_clicks\_counted"). ### Long Clicks A long click event is fired after a button is held down for the specified amount of time. The event also allows two [`ClickHandler`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler "ClickHandler")s to be registered - one for when the button is pressed, and another for when the button is released. Only one of these is required. ``` static void click\_config\_provider(void \*context) {ButtonId id = BUTTON\_ID\_SELECT;// The select buttonuint16\_t delay\_ms = 500;// Minimum time pressed to firewindow\_long\_click\_subscribe(id, delay\_ms, long\_down\_click\_handler,long\_up\_click\_handler);} ``` ### Raw Clicks The last type of button click subcsription is used to track raw button click events. Like the long click event, two [`ClickHandler`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler "ClickHandler")s may be supplied to receive each of the pressed and depressed events. ``` static void click\_config\_provider(void \*context) {ButtonId id = BUTTON\_ID\_SELECT;// The select buttonwindow\_raw\_click\_subscribe(id, raw\_down\_click\_handler, raw\_up\_click\_handler,NULL);} ``` > The last parameter is an optional pointer to a context object to be passed to the callback, and is set to `NULL` if not used. --- # Compass Source: https://developer.repebble.com/guides/events-and-services/compass/ The [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService") combines data from Pebble's accelerometer and magnetometer to automatically calibrate the compass and produce a [`CompassHeading`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeading "CompassHeading"), containing an angle measured relative to magnetic north. The compass service provides magnetic north and information about its status and accuracy through the [`CompassHeadingData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData "CompassHeadingData") structure. ## Calibration The compass service requires an initial calibration before it can return accurate results. Calibration is performed automatically by the system when first required. The [`compass_status`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData "CompassHeadingData") field indicates whether the compass service is calibrating. To help the calibration process, the app should show a message to the user asking them to move their wrists in different directions. Refer to the [compass example](https://github.com/pebble-examples/feature-compass) for an example of how to implement this screen. Calibration only runs while at least one app is subscribed to the compass service. If all apps unsubscribe before calibration is complete, the calibration is discarded and will start again from scratch the next time an app subscribes. Once calibration succeeds, it is saved for future reuse by all apps without recalibration. Putting the pebble on charge deletes any saved calibration. ## Magnetic North and True North Depending on the user's location on Earth, the measured heading towards magnetic north and true north can significantly differ. This is due to magnetic variation, also known as 'declination'. Pebble does not automatically correct the magnetic heading to return a true heading, but the API is designed so that this feature can be added in the future and the app will be able to automatically take advantage of it. For a more precise heading, use the `magnetic_heading` field of [`CompassHeadingData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData "CompassHeadingData") and use a webservice to retrieve the declination at the user's current location. Otherwise, use the `true_heading` field. This field will contain the `magnetic_heading` if declination is not available, or the true heading if declination is available. The field `is_declination_valid` will be true when declination is available. Use this information to tell the user whether the app is showing magnetic north or true north. ![Declination illustrated](https://developer.repebble.com/assets/images/guides/pebble-apps/sensors/declination.gif) > To see the true extent of declination, see how declination has [changed over time](http://maps.ngdc.noaa.gov/viewers/historical_declination/). ## Battery Considerations Using the compass will turn on both Pebble's magnetometer and accelerometer. Those two devices will have a slight impact on battery life. A much more significant battery impact will be caused by redrawing the screen too often or performing CPU-intensive work every time the compass heading is updated. Use [`compass_service_subscribe()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_subscribe "compass\_service\_subscribe") if the app only needs to update its UI when new compass data is available, or else use [`compass_service_peek()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_peek "compass\_service\_peek") if this happens much less frequently. ## Defining "Up" on Pebble Compass readings are always relative to the current orientation of Pebble. Using the accelerometer, the compass service figures out which direction the user is facing. ![Compass Orientation](https://developer.repebble.com/assets/images/guides/pebble-apps/sensors/compass-orientation.png) The best orientation to encourage users to adopt while using a compass-enabled watchapp is with the top of the watch parallel to the ground. If the watch is raised so that the screen is facing the user, the plane will now be perpedicular to the screen, but still parallel to the ground. ## Angles and Degrees The magnetic heading value is presented as a number between 0 and TRIG\_MAX\_ANGLE (65536). This range is used to give a higher level of precision for drawing commands, which is preferable to using only 360 degrees. If you imagine an analogue clock face on your Pebble, the angle 0 is always at the 12 o'clock position, and the magnetic heading angle is calculated in a counter clockwise direction from 0. This can be confusing to grasp at first, as it’s opposite of how direction is measured on a compass, but it's simple to convert the values into a clockwise direction: ``` int clockwise\_angle = TRIG\_MAX\_ANGLE - heading\_data.magnetic\_heading; ``` Once you have an angle relative to North, you can convert that to degrees using the helper function [`TRIGANGLE_TO_DEG()`](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIGANGLE_TO_DEG "TRIGANGLE\_TO\_DEG"): ``` int degrees = TRIGANGLE\_TO\_DEG(TRIG\_MAX\_ANGLE - heading\_data.magnetic\_heading); ``` ## Subscribing to Compass Data Compass heading events can be received in a watchapp by subscribing to the [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService"): ``` // Subscribe to compass heading updatescompass\_service\_subscribe(compass\_heading\_handler); ``` The provided [`CompassHeadingHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingHandler "CompassHeadingHandler") function (called `compass_heading_handler` above) can be used to read the state of the compass, and the current heading if it is available. This value is given in the range of `0` to [`TRIG_MAX_ANGLE`](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_ANGLE "TRIG\_MAX\_ANGLE") to preserve precision, and so it can be converted using the [`TRIGANGLE_TO_DEG()`](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIGANGLE_TO_DEG "TRIGANGLE\_TO\_DEG") macro: ``` static void compass\_heading\_handler(CompassHeadingData heading\_data) {// Is the compass calibrated?switch(heading\_data.compass\_status) {case CompassStatusDataInvalid:APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Not yet calibrated.");break;case CompassStatusCalibrating:APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Calibration in progress. Heading is %ld",TRIGANGLE\_TO\_DEG(TRIG\_MAX\_ANGLE - heading\_data.magnetic\_heading));break;case CompassStatusCalibrated:APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Calibrated! Heading is %ld",TRIGANGLE\_TO\_DEG(TRIG\_MAX\_ANGLE - heading\_data.magnetic\_heading));break;}} ``` By default, the callback will be triggered whenever the heading changes by one degree. To reduce the frequency of updates, change the threshold for heading changes by setting a heading filter: ``` // Only notify me when the heading has changed by more than 5 degrees.compass\_service\_set\_heading\_filter(DEG\_TO\_TRIGANGLE(5)); ``` ## Unsubscribing From Compass Data When the app is done using the compass, stop receiving callbacks by unsubscribing: ``` compass\_service\_unsubscribe(); ``` ## Peeking at Compass Data To fetch a compass heading, simply peek to get a single sample: ``` // Peek to get dataCompassHeadingData data;compass\_service\_peek(&data); ``` If not already subscribed, this temporarily subscribes the app to the compass service without a handler. WARNING: Regardless of if you were already subscribed with a handler or not, calling `compass_service_peek()` sets a timer to unsubscribe after 11 seconds (refreshed on each call). > Similar to the subscription-provided data, the app should examine the peeked [`CompassHeadingData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData "CompassHeadingData") to determine if it is valid (i.e. the compass is calibrated). Note that as calibration requires an active subscription, and calibration can take longer than 11 seconds, to calibrate while only using peek you must keep calling `compass_service_peek()` over time to refresh its unsubscribe timer until calibration is successful. --- # Dictation Source: https://developer.repebble.com/guides/events-and-services/dictation/ **PLATFORM NOTICE** This guide does not apply to apps built to run on the Aplite platform (Pebble Classic and Pebble Steel). On hardware [platforms](https://developer.repebble.com/faqs.md#pebble-sdk) supporting a microphone, the [`Dictation`](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") API can be used to gather arbitrary text input from a user. This approach is much faster than any previous button-based text input system (such as [tertiary text](https://github.com/vgmoose/tertiary_text)), and includes the ability to allow users to re-attempt dictation if there are any errors in the returned transcription. > Note: Apps running on multiple hardware platforms that may or may not include a microphone should use the `PBL_MICROPHONE` compile-time define (as well as checking API return values) to gracefully handle when it is not available. ## How the Dictation API Works The [`Dictation`](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") API invokes the same UI that is shown to the user when responding to notifications via the system menu, with events occuring in the following order: - The user initiates transcription and the dictation UI is displayed. - The user dictates the phrase they would like converted into text. - The audio is transmitted via the Pebble phone application to a 3rd party service and translated into text. - When the text is returned, the user is given the opportunity to review the result of the transcription. At this time they may elect to re-attempt the dictation by pressing the Back button and speaking clearer. - When the user is happy with the transcription, the text is provided to the app by pressing the Select button. - If an error occurs in the transcription attempt, the user is automatically allowed to re-attempt the dictation. - The user can retry their dictation by rejecting a successful transcription, but only if confirmation dialogs are enabled. ## Beginning a Dictation Session To get voice input from a user, an app must first create a [`DictationSession`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession "DictationSession") that contains data relating to the status of the dictation service, as well as an allocated buffer to store the result of any transcriptions. This should be declared in the file-global scope (as `static`), so it can be used at any time (in button click handlers, for example). ``` static DictationSession \*s\_dictation\_session; ``` A callback of type [`DictationSessionStatusCallback`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback "DictationSessionStatusCallback") is also required to notify the developer to the status of any dictation requests and transcription results. This is called at any time the dictation UI exits, which can be for any of the following reasons: - The user accepts a transcription result. - A transcription is successful but the confirmation dialog is disabled. - The user exits the dictation UI with the Back button. - When any error occurs and the error dialogs are disabled. - Too many transcription errors occur. ``` static void dictation\_session\_callback(DictationSession \*session, DictationSessionStatus status,char \*transcription, void \*context) {// Print the results of a transcription attemptAPP\_LOG(APP\_LOG\_LEVEL\_INFO, "Dictation status: %d", (int)status);} ``` At the end of this callback the `transcription` pointer becomes invalid - if the text is required later it should be copied into a separate buffer provided by the app. The size of this dictation buffer is chosen by the developer, and should be large enough to accept all expected input. Any transcribed text longer than the length of the buffer will be truncated. ``` // Declare a buffer for the DictationSessionstatic char s\_last\_text[512]; ``` Finally, create the [`DictationSession`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession "DictationSession") and supply the size of the buffer and the [`DictationSessionStatusCallback`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback "DictationSessionStatusCallback"). This session may be used as many times as requires for multiple transcriptions. A context pointer may also optionally be provided. ``` // Create new dictation sessions\_dictation\_session = dictation\_session\_create(sizeof(s\_last\_text),dictation\_session\_callback, NULL); ``` ## Obtaining Dictated Text After creating a [`DictationSession`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession "DictationSession"), the developer can begin a dictation attempt at any time, providing that one is not already in progress. ``` // Start dictation UIdictation\_session\_start(s\_dictation\_session); ``` The dictation UI will be displayed and the user will speak their desired input. ![listening](https://developer.repebble.com/assets/images/guides/pebble-apps/sensors/listening.png) It is recommended to provide visual guidance on the format of the expected input before the [`dictation_session_start()`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_start "dictation\_session\_start") is called. For example, if the user is expected to speak a location that should be a city name, they should be briefed as such before being asked to provide input. When the user exits the dictation UI, the developer's [`DictationSessionStatusCallback`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback "DictationSessionStatusCallback") will be called. The `status` parameter provided will inform the developer as to whether or not the transcription was successful using a [`DictationSessionStatus`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatus "DictationSessionStatus") value. It is useful to check this value, as there are multiple reasons why a dictation request may not yield a successful result. These values are described below under [_DictationSessionStatus Values_](#dictationsessionstatus-values). If the value of `status` is equal to [`DictationSessionStatusSuccess`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusSuccess "DictationSessionStatusSuccess"), the transcription was successful. The user's input can be read from the `transcription` parameter for evaluation and storage for later use if required. Note that once the callback returns, `transcription` will no longer be valid. For example, a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") in the app's UI with variable name `s_output_layer` may be used to show the status of an attempted transcription: ``` if(status == DictationSessionStatusSuccess) {// Display the dictated textsnprintf(s\_last\_text, sizeof(s\_last\_text), "Transcription:\n\n%s", transcription);text\_layer\_set\_text(s\_output\_layer, s\_last\_text);} else {// Display the reason for any errorstatic char s\_failed\_buff[128];snprintf(s\_failed\_buff, sizeof(s\_failed\_buff), "Transcription failed.\n\nReason:\n%d",(int)status);text\_layer\_set\_text(s\_output\_layer, s\_failed\_buff);} ``` The confirmation mechanism allowing review of the transcription result can be disabled if it is not needed. An example of such a scenario may be to speed up a 'yes' or 'no' decision where the two expected inputs are distinct and different. ``` // Disable the confirmation screendictation\_session\_enable\_confirmation(s\_dictation\_session, false); ``` It is also possible to disable the error dialogs, if so desired. This will disable the dialogs that appear when a transcription attempt fails, as well as disabling the ability to retry the dictation if a failure occurs. ``` // Disable error dialogsdictation\_session\_enable\_error\_dialogs(s\_dictation\_session, false); ``` ### DictationSessionStatus Values These are the possible values provided by a [`DictationSessionStatusCallback`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback "DictationSessionStatusCallback"), and should be used to handle transcription success or failure for any of the following reasons. | Status | Value | Description | | --- | --- | --- | | [`DictationSessionStatusSuccess`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusSuccess "DictationSessionStatusSuccess") | `0` | Transcription successful, with a valid result. | | [`DictationSessionStatusFailureTranscriptionRejected`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureTranscriptionRejected "DictationSessionStatusFailureTranscriptionRejected") | `1` | User rejected transcription and dismissed the dictation UI. | | [`DictationSessionStatusFailureTranscriptionRejectedWithError`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureTranscriptionRejectedWithError "DictationSessionStatusFailureTranscriptionRejectedWithError") | `2` | User exited the dictation UI after a transcription error. | | [`DictationSessionStatusFailureSystemAborted`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureSystemAborted "DictationSessionStatusFailureSystemAborted") | `3` | Too many errors occurred during transcription and the dictation UI exited. | | [`DictationSessionStatusFailureNoSpeechDetected`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureNoSpeechDetected "DictationSessionStatusFailureNoSpeechDetected") | `4` | No speech was detected and the dictation UI exited. | | [`DictationSessionStatusFailureConnectivityError`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureConnectivityError "DictationSessionStatusFailureConnectivityError") | `5` | No Bluetooth or Internet connection available. | | [`DictationSessionStatusFailureDisabled`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureDisabled "DictationSessionStatusFailureDisabled") | `6` | Voice transcription disabled for this user. This can occur if the user has disabled sending 'Usage logs' in the Pebble mobile app. | | [`DictationSessionStatusFailureInternalError`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureInternalError "DictationSessionStatusFailureInternalError") | `7` | Voice transcription failed due to an internal error. | | [`DictationSessionStatusFailureRecognizerError`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureRecognizerError "DictationSessionStatusFailureRecognizerError") | `8` | Cloud recognizer failed to transcribe speech (only possible if error dialogs are disabled). | --- # Event Services Source: https://developer.repebble.com/guides/events-and-services/events/ All Pebble apps are executed in three phases, which are summarized below: - Initialization - all code from the beginning of `main()` is run to set up all the components of the app. - Event Loop - the app waits for and responds to any event services it has subscribed to. - Deinitialization - when the app is exiting (i.e.: the user has pressed Back from the last [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") in the stack) [`app_event_loop()`](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop "app\_event\_loop") returns, and all deinitialization code is run before the app exits. Once [`app_event_loop()`](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop "app\_event\_loop") is called, execution of `main()` pauses and all further activities are performed when events from various [`Event Service`](https://developer.repebble.com/docs/c/Foundation/Event_Service.md "Event Service") types occur. This continues until the app is exiting, and is typically handled in the following pattern: ``` static void init() {// Initialization code here}static void deinit() {// Deinitialization code here}int main(void) {init();app\_event\_loop();deinit();} ``` ## Types of Events There are multiple types of events an app can receive from various event services. These are described in the table below, along with their handler signature and a brief description of what they do: | Event Service | Handler(s) | Description | | --- | --- | --- | | [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") | [`TickHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler "TickHandler") | Most useful for watchfaces. Allows apps to be notified when a second, minute, hour, day, month or year ticks by. | | [`ConnectionService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md "ConnectionService") | [`ConnectionHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#ConnectionHandler "ConnectionHandler") | Allows apps to know when the Bluetooth connection with the phone connects and disconnects. | | [`AccelerometerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md "AccelerometerService") | [`AccelDataHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelDataHandler "AccelDataHandler") [`AccelTapHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelTapHandler "AccelTapHandler") | Allows apps to receive raw data or tap events from the onboard accelerometer. | | [`BatteryStateService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md "BatteryStateService") | [`BatteryStateHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryStateHandler "BatteryStateHandler") | Allows apps to read the state of the battery, as well as whether the watch is plugged in and charging. | | [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") | [`HealthEventHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHandler "HealthEventHandler") | Allows apps to be notified to changes in various [`HealthMetric`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric "HealthMetric") values as the user performs physical activities. | | [`AppFocusService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md "AppFocusService") | [`AppFocusHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandler "AppFocusHandler") | Allows apps to know when they are obscured by another window, such as when a notification modal appears. | | [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService") | [`CompassHeadingHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingHandler "CompassHeadingHandler") | Allows apps to read a compass heading, including calibration status of the sensor. | | [`BacklightService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md "BacklightService") | [`BacklightHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#BacklightHandler "BacklightHandler") | Allows apps to know when the backlight turns on or off. See [_Backlight_](https://developer.repebble.com/guides/events-and-services/light.md). | | [`AlarmService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md "AlarmService") | None | Lets apps peek at the next alarm the user has set. | In addition, many other APIs also operate through the use of various callbacks including [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer"), [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"), [`Timer`](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer"), and [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup"), but these are not considered to be 'event services' in the same sense. ## Using Event Services The event services described in this guide are all used in the same manner - the app subscribes an implementation of one or more handlers, and is notified by the system when an event of that type occurs. In addition, most also include a 'peek' style API to read a single data item or status value on demand. This can be useful to determine the initial service state when a watchapp starts. Apps can subscribe to as many of these services as they require, and can also unsubscribe at any time to stop receiving events. Each event service is briefly discussed below with multiple snippets - handler implementation example, subscribing to the service, and any 'peek' API. ### Tick Timer Service The [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") allows an app to be notified when different units of time change. This is decided based upon the [`TimeUnits`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TimeUnits "TimeUnits") value specified when a subscription is added. The [`struct tm`](http://www.cplusplus.com/reference/ctime/tm/) pointer provided in the handler is a standard C object that contains many data fields describing the current time. This can be used with [`strftime()`](http://www.cplusplus.com/reference/ctime/strftime/) to obtain a human-readable string. ``` static void tick\_handler(struct tm \*tick\_time, TimeUnits changed) {static char s\_buffer[8];// Read time into a string bufferstrftime(s\_buffer, sizeof(s\_buffer), "%H:%M", tick\_time);APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Time is now %s", s\_buffer);} ``` ``` // Get updates when the current minute changestick\_timer\_service\_subscribe(MINUTE\_UNIT, tick\_handler); ``` > The [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") has no 'peek' API, but a similar effect can be achieved using the [`time()`](https://developer.repebble.com/docs/c/Standard_C/Time.md "Time") and [`localtime()`](https://developer.repebble.com/docs/c/Standard_C/Time.md#localtime "localtime") APIs. ### Connection Service The [`ConnectionService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md "ConnectionService") uses a handler for each of two connection types: - `pebble_app_connection_handler` - the connection to the Pebble app on the phone, analogous with the bluetooth connection state. - `pebblekit_connection_handler` - the connection to an iOS companion app, if applicable. Will never occur on Android. Either one is optional, but at least one must be specified for a valid subscription. ``` static void app\_connection\_handler(bool connected) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Pebble app %sconnected", connected ? "" : "dis");}static void kit\_connection\_handler(bool connected) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "PebbleKit %sconnected", connected ? "" : "dis"); } ``` ``` connection\_service\_subscribe((ConnectionHandlers) {.pebble\_app\_connection\_handler = app\_connection\_handler,.pebblekit\_connection\_handler = kit\_connection\_handler}); ``` ``` // Peek at either the Pebble app or PebbleKit connectionsbool app\_connection = connection\_service\_peek\_pebble\_app\_connection();bool kit\_connection = connection\_service\_peek\_pebblekit\_connection(); ``` ### Accelerometer Service The [`AccelerometerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md "AccelerometerService") can be used in two modes - tap events and raw data events. [`AccelTapHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelTapHandler "AccelTapHandler") and [`AccelDataHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelDataHandler "AccelDataHandler") are used for each of these respective use cases. See the [_Accelerometer_](https://developer.repebble.com/guides/events-and-services/accelerometer.md) guide for more information. **Data Events** ``` static void accel\_data\_handler(AccelData \*data, uint32\_t num\_samples) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Got %d new samples", (int)num\_samples);} ``` ``` const int num\_samples = 10;// Subscribe to data eventsaccel\_data\_service\_subscribe(num\_samples, accel\_data\_handler); ``` ``` // Peek at the last readingAccelData data;accel\_service\_peek(&data); ``` **Tap Events** ``` static void accel\_tap\_handler(AccelAxisType axis, int32\_t direction) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Tap event received");} ``` ``` // Subscribe to tap eventsaccel\_tap\_service\_subscribe(accel\_tap\_handler); ``` ### Battery State Service The [`BatteryStateService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md "BatteryStateService") allows apps to examine the state of the battery, and whether or not is is plugged in and charging. ``` static void battery\_state\_handler(BatteryChargeState charge) {// Report the current charge percentageAPP\_LOG(APP\_LOG\_LEVEL\_INFO, "Battery charge is %d%%", (int)charge.charge\_percent);} ``` ``` // Get battery state updatesbattery\_state\_service\_subscribe(battery\_state\_handler); ``` ``` // Peek at the current battery stateBatteryChargeState state = battery\_state\_service\_peek(); ``` ### Health Service The [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") uses the [`HealthEventHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHandler "HealthEventHandler") to notify a subscribed app when new data pertaining to a [`HealthMetric`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric "HealthMetric") is available. See the [_Pebble Health_](https://developer.repebble.com/guides/events-and-services/health.md) guide for more information. ``` static void health\_handler(HealthEventType event, void \*context) {if(event == HealthEventMovementUpdate) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "New health movement event");}} ``` ``` // Subscribe to health-related eventshealth\_service\_events\_subscribe(health\_handler, NULL); ``` ### App Focus Service The [`AppFocusService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md "AppFocusService") operates in two modes - basic and complete. **Basic Subscription** A basic subscription involves only one handler which will be fired when the app is moved in or out of focus, and any animated transition has completed. ``` static void focus\_handler(bool in\_focus) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "App is %s in focus", in\_focus ? "now" : "not");} ``` ``` // Add a basic subscriptionapp\_focus\_service\_subscribe(focus\_handler); ``` **Complete Subscription** A complete subscription will notify the app with more detail about changes in focus using two handlers in an [`AppFocusHandlers`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers "AppFocusHandlers") object: - `.will_focus` - represents a change in focus that is _about_ to occur, such as the start of a transition animation to or from a modal window. `will_focus` will be `true` if the app will be in focus at the end of the transition. - `.did_focus` - represents the end of a transition. `did_focus` will be `true` if the app is now completely in focus and the animation has finished. ``` void will\_focus\_handler(bool will\_focus) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Will %s focus", will\_focus ? "gain" : "lose");}void did\_focus\_handler(bool did\_focus) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "%s focus", did\_focus ? "Gained" : "Lost");} ``` ``` // Subscribe to both types of eventsapp\_focus\_service\_subscribe\_handlers((AppFocusHandlers) {.will\_focus = will\_focus\_handler,.did\_focus = did\_focus\_handler}); ``` ### Alarm Service The [`AlarmService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md "AlarmService") has no handler. [`alarm_service_peek_next()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md#alarm_service_peek_next "alarm\_service\_peek\_next") reports whether at least one enabled alarm is scheduled and writes the UTC time it fires, which is enough for a watchface to show an alarm indicator: ``` time\_t next\_alarm;if (alarm\_service\_peek\_next(&next\_alarm)) {struct tm \*t = localtime(&next\_alarm);strftime(s\_alarm\_buffer, sizeof(s\_alarm\_buffer), "%H:%M", t);text\_layer\_set\_text(s\_alarm\_layer, s\_alarm\_buffer);} else {text\_layer\_set\_text(s\_alarm\_layer, "");} ``` Alarms cannot be created or changed from an app. The function is available on Pebble 2 Duo, Pebble Time 2 and Pebble Round 2; on older platforms it compiles to a stub that returns `false`. ### Compass Service The [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService") provides access to regular updates about the watch's magnetic compass heading, if it is calibrated. See the [_Compass_](https://developer.repebble.com/guides/events-and-services/compass.md) guide for more information. ``` static void compass\_heading\_handler(CompassHeadingData heading\_data) {// Is the compass calibrated?if(heading\_data.compass\_status == CompassStatusCalibrated) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Calibrated! Heading is %ld",TRIGANGLE\_TO\_DEG(heading\_data.magnetic\_heading));}} ``` ``` // Subscribe to compass heading updatescompass\_service\_subscribe(compass\_heading\_handler); ``` ``` // Peek the compass heading dataCompassHeadingData data;compass\_service\_peek(&data); ``` --- # Events and Services Source: https://developer.repebble.com/guides/events-and-services/ All Pebble watches contain a collection of sensors than can be used as input devices for apps. Available sensors include four buttons, an accelerometer, and a magnetometer (accessible via the [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService") API). In addition, the Basalt and Chalk platforms also include a microphone (accessible via the [`Dictation`](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") API) and access to Pebble Health data sets. Read [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) for more information on sensor availability per platform. While providing more interactivity, excessive regular use of these sensors will stop the watch's CPU from sleeping and result in faster battery drain, so use them sparingly. An alternative to constantly reading accelerometer data is to obtain data in batches, allowing sleeping periods in between. Read [_Conserving Battery Life_](https://developer.repebble.com/guides/best-practices/conserving-battery-life.md) for more information. ## Contents - [**Accelerometer**](https://developer.repebble.com/guides/events-and-services/accelerometer.md) - How to use data and simple tap gestures from the onboard accelerometer. - [**Background Worker**](https://developer.repebble.com/guides/events-and-services/background-worker.md) - Using the Background Worker to do work in the background, such as activity tracking. - [**Backlight**](https://developer.repebble.com/guides/events-and-services/light.md) - How to trigger the backlight, force it on, and - on hardware with an RGB backlight - tint it. - [**Buttons**](https://developer.repebble.com/guides/events-and-services/buttons.md) - How to react to button presses in your app. - [**Compass**](https://developer.repebble.com/guides/events-and-services/compass.md) - How to use data from the Compass API to determine direction. - [**Dictation**](https://developer.repebble.com/guides/events-and-services/dictation.md) - How to use the Dictation API to get voice-to-text input in watchapps. - [**Event Services**](https://developer.repebble.com/guides/events-and-services/events.md) - How to use the various asynchronous event services to power app features. - [**Heart Rate Monitor**](https://developer.repebble.com/guides/events-and-services/hrm.md) - Information on using the HealthService API to obtain information from the Heart Rate Monitor. - [**Pebble Health**](https://developer.repebble.com/guides/events-and-services/health.md) - Information on using the HealthService API to incorporate multiple types of health data into your apps. - [**Persistent Storage**](https://developer.repebble.com/guides/events-and-services/persistent-storage.md) - Using persistent storage to improve your app's UX. - [**Speaker**](https://developer.repebble.com/guides/events-and-services/speaker.md) - How to play tones, melodies, polyphonic tracks, and PCM streams through the speaker. - [**Touch**](https://developer.repebble.com/guides/events-and-services/touch.md) - How to subscribe to touch events on platforms with a touchscreen. - [**Wakeups**](https://developer.repebble.com/guides/events-and-services/wakeups.md) - Using the Wakeup API to launch an app at some future time. --- # Heart Rate Monitor Source: https://developer.repebble.com/guides/events-and-services/hrm/ The Pebble Time 2 and Pebble 2 (excluding SE model) [_devices_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) include a heart rate monitor. This guide will demonstrate how to use the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") API to retrieve information about the user's current, and historical heart rates. See the platform support table in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md#platform-support) for which watches have a heart rate monitor. If you aren't already familiar with the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService"), we recommended that you read the [_Health guide_](https://developer.repebble.com/guides/events-and-services/health.md) before proceeding. ## Enable Health Data Before your application is able to access the heart rate information, you will need to add `heath` to the `capabilities` array in your applications `package.json` file. ``` {..."pebble": {..."capabilities": ["health"],...}} ``` ## Data Quality Heart rate sensors aren't perfect, and their readings can be affected by improper positioning, environmental factors and excessive movement. The raw data from the HRM sensor contains a metric to indicate the quality of the readings it receives. The HRM API provides a raw BPM reading ([`HealthMetricHeartRateRawBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateRawBPM "HealthMetricHeartRateRawBPM")) and a filtered reading ([`HealthMetricHeartRateBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateBPM "HealthMetricHeartRateBPM")). This filtered value minimizes the effect of hand movement and improper sensor placement, by removing the bad quality readings. This filtered data makes it easy for developers to integrate in their applications, without needing to filter the data themselves. ## Obtaining the Current Heart Rate To obtain the current heart rate, you should first check whether the [`HealthMetricHeartRateBPM`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateBPM "HealthMetricHeartRateBPM") is available by using the [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") method. Then you can obtain the current heart rate using the [`health_service_peek_current_value`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_peek_current_value "health\_service\_peek\_current\_value") method: ``` HealthServiceAccessibilityMask hr = health\_service\_metric\_accessible(HealthMetricHeartRateBPM, time(NULL), time(NULL));if (hr & HealthServiceAccessibilityMaskAvailable) {HealthValue val = health\_service\_peek\_current\_value(HealthMetricHeartRateBPM);if(val \> 0) {// Display HRM valuestatic char s\_hrm\_buffer[8];snprintf(s\_hrm\_buffer, sizeof(s\_hrm\_buffer), "%lu BPM", (uint32\_t)val);text\_layer\_set\_text(s\_hrm\_layer, s\_hrm\_buffer);}} ``` > **Note** this value is averaged from readings taken over the past minute, but due to the [sampling rate](#heart-rate-sample-periods) and our data filters, this value could be several minutes old. Use `HealthMetricHeartRateRawBPM` for the raw, unfiltered value. ## Subscribing to Heart Rate Updates The user's heart rate can also be monitored via an event subscription, in a similar way to the other health metrics. If you wanted your watchface to update the displayed heart rate every time the HRM takes a new reading, you could use the [`health_service_events_subscribe`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_subscribe "health\_service\_events\_subscribe") method. ``` static void prv\_on\_health\_data(HealthEventType type, void \*context) {// If the update was from the Heart Rate Monitor, query itif (type == HealthEventHeartRateUpdate) {HealthValue value = health\_service\_peek\_current\_value(HealthMetricHeartRateBPM);// Display the heart rate}}static void prv\_init(void) {// Subscribe to health event handlerhealth\_service\_events\_subscribe(prv\_on\_health\_data, NULL);// ...} ``` > **Note** The frequency of these updates does not directly correlate to the sensor sampling rate. ## Heart Rate Sample Periods The default sample period is 10 minutes, but the system automatically controls the HRM sample rate based on the level of user activity. It increases the sampling rate during intense activity and reduces it again during inactivity. This aims to provide the optimal battery usage. ### Battery Considerations Like all active sensors, accelerometer, backlight etc, the HRM sensor will have a negative affect on battery life. It's important to consider this when using the APIs within your application. By default the system will automatically control the heart rate sampling period for the optimal balance between update frequency and battery usage. In addition, the APIs have been designed to allow developers to retrieve values for the most common use cases with minimal impact on battery life. ### Altering the Sampling Period Developers can request a specific sampling rate using the [`health_service_set_heart_rate_sample_period`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_set_heart_rate_sample_period "health\_service\_set\_heart\_rate\_sample\_period") method. The system will use this value as a suggestion, but does not guarantee that value will be used. The actual sampling period may be greater or less due to other apps that require input from the sensor, or data quality issues. The shortest period you can currently specify is `1` second, and the longest period you can specify is `600` seconds (10 minutes). In this example, we will sample the heart rate monitor every second: ``` // Set the heart rate monitor to sample every secondbool success = health\_service\_set\_heart\_rate\_sample\_period(1); ``` > **Note** This does not mean that you can peek the current value every second, only that the sensor will capture more samples. ### Resetting the Sampling Period Developers **must** reset the heart rate sampling period when their application exits. Failing to do so may result in the heart rate monitor continuing at the increased rate for a period of time, even after your application closes. This is fundamentally different to other Pebble sensors and was designed so that applications which a reliant upon high sampling rates can be temporarily interupted for notifications, or music, without affecting the sensor data. ``` // Reset the heart rate sampling period to automatichealth\_service\_set\_heart\_rate\_sample\_period(0); ``` ## Heart Rate Variability Since firmware 4.32, watches whose heart rate sensor supports it (currently the Pebble Time 2) can also report heart rate variability (HRV) as peak-to-peak intervals (PPI). Unlike the BPM metrics, HRV data is only collected while an app explicitly requests it. To start collecting HRV readings, request an HRV sample period with `health_service_set_hrv_sample_period`. While any app holds an HRV sample period, the sensor collects HRV alongside heart rate, and a [`HealthEventHRVUpdate`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHRVUpdate "HealthEventHRVUpdate") event is delivered to health service subscribers each time a new reading is available. Read the most recent interval with `health_service_peek_hrv_ppi_ms`, which returns the PPI in milliseconds, or `0` if no reading is available yet: ``` static void prv\_on\_health\_data(HealthEventType type, void \*context) {if (type == HealthEventHRVUpdate) {uint16\_t ppi = health\_service\_peek\_hrv\_ppi\_ms();if (ppi \> 0) {// Display the peak-to-peak interval}}}static void prv\_init(void) {// Subscribe to health event handlerhealth\_service\_events\_subscribe(prv\_on\_health\_data, NULL);// Collect an HRV reading every 10 secondshealth\_service\_set\_hrv\_sample\_period(10);} ``` The HRV and heart rate sample periods are independent requests that share the app's single sensor subscription: both may be active at once, the sensor is driven at the shorter of the two periods, and setting one period to `0` clears only that request. As with the heart rate sample period, reset the HRV sample period when your application no longer needs it: ``` // Stop requesting HRV samplinghealth\_service\_set\_hrv\_sample\_period(0); ``` ## Obtaining Historical Data If your application is using heart rate information, it may also want to obtain historical data to compare it against. In this section we'll look at how you can use the `health_service_aggregate` functions to obtain relevant historic data. Before requesting historic/aggregate data for a specific time period, you should ensure that it is available using the [`health_service_metric_accessible`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible "health\_service\_metric\_accessible") method. Then we'll use the [`health_service_aggregate_averaged`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged "health\_service\_aggregate\_averaged") method to obtain the average daily heart rate over the last 7 days. ``` // Obtain history for last 7 daystime\_t end\_time = time(NULL);time\_t start\_time = end\_time - (7 \* SECONDS\_PER\_DAY);HealthServiceAccessibilityMask hr = health\_service\_metric\_accessible(HealthMetricHeartRateBPM, start\_time, end\_time);if (hr & HealthServiceAccessibilityMaskAvailable) {uint32\_t weekly\_avg\_hr = health\_service\_aggregate\_averaged(HealthMetricHeartRateBPM,start\_time, end\_time,HealthAggregationAvg, HealthServiceTimeScopeDaily);} ``` You can also query the average `min` and `max` heart rates, but only within the past two hours (maximum). This limitation is due to very limited storage capacity on the device, but the implementation may change in the future. ``` // Obtain history for last 1 hourtime\_t end\_time = time(NULL);time\_t start\_time = end\_time - SECONDS\_PER\_HOUR;HealthServiceAccessibilityMask hr = health\_service\_metric\_accessible(HealthMetricHeartRateBPM, start\_time, end\_time);if (hr & HealthServiceAccessibilityMaskAvailable) {uint32\_t min\_hr = health\_service\_aggregate\_averaged(HealthMetricHeartRateBPM,start\_time, end\_time,HealthAggregationMin, HealthServiceTimeScopeOnce);uint32\_t max\_hr = health\_service\_aggregate\_averaged(HealthMetricHeartRateBPM,start\_time, end\_time,HealthAggregationMax, HealthServiceTimeScopeOnce);} ``` ## Read Per-Minute History The [`HealthMinuteData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMinuteData "HealthMinuteData") structure contains multiple types of activity-related data that are recorded in a minute-by-minute fashion. Although this structure now contains HRM readings, it does not contain information about the quality of those readings. > **Note** Please refer to the [_Health Guide_](https://developer.repebble.com/guides/events-and-services/health.md#read-per-minute-history) for futher information. ## Next Steps This guide covered the basics of how to interact with realtime and historic heart information. We encourage you to further explore the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") documentation, and integrate it into your next project. --- # Pebble Health Source: https://developer.repebble.com/guides/events-and-services/health/ **PLATFORM NOTICE** This guide does not apply to apps built to run on the Aplite platform (Pebble Classic and Pebble Steel). [Pebble Health](https://blog.getpebble.com/2015/12/15/health/) provides builtin health data tracking to allow users to improve their activity and sleep habits. With SDK 3.9, the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") API opens this data up to developers to include and use within their apps. For example, a watchface could display a brief summary of the user's activity for the day. ## API Availability In order to use the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") (and indeed Pebble Health), the user must enable the 'Pebble Health' app in the 'Apps/Timeline' view of the official Pebble mobile app. If this is not enabled health data will not be available to apps, and API calls will return values to reflect this. In addition, any app using the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") API must declare the 'health' capability in order to be accepted by the [developer dashboard](https://developer.repebble.com/dashboard). This can be done in `package.json` in the SDK: ``` "capabilities": ["health"] ``` Since Pebble Health is not available on the Aplite platform, developers should check the API return values and hence the lack of [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") on that platform gracefully. In addition, the `PBL_HEALTH` define and `PBL_IF_HEALTH_ELSE()` macro can be used to selectively omit affected code. ## Available Metrics The [`HealthMetric`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric "HealthMetric") `enum` lists the types of data (or 'metrics') that can be read using the API. These are described below: | Metric | Description | | --- | --- | | `HealthMetricStepCount` | The user's step count. | | `HealthMetricActiveSeconds` | Duration of time the user was considered 'active'. | | `HealthMetricWalkedDistanceMeters` | Estimation of the distance travelled in meters. | | `HealthMetricSleepSeconds` | Duration of time the user was considered asleep. | | `HealthMetricSleepRestfulSeconds` | Duration of time the user was considered in deep restful sleep. | | `HealthMetricRestingKCalories` | The number of kcal (thousand calories) burned due to resting metabolism. | | `HealthMetricActiveKCalories` | The number of kcal (thousand calories) burned due to activity. | | `HealthMetricHeartRateBPM` | The heart rate, in beats per minute. | ## Subscribing to HealthService Events Like other Event Services, an app can subscribe a handler function to receive a callback when new health data is available. This is useful for showing near-realtime activity updates. The handler must be a suitable implementation of [`HealthEventHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHandler "HealthEventHandler"). The `event` parameter describes the type of each update, and is one of the following from the [`HealthEventType`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventType "HealthEventType") `enum`: | Event Type | Value | Description | | --- | --- | --- | | `HealthEventSignificantUpdate` | `0` | All data is considered as outdated, apps should re-read all health data. This can happen on a change of the day or in other cases that significantly change the underlying data. | | `HealthEventMovementUpdate` | `1` | Recent values around `HealthMetricStepCount`, `HealthMetricActiveSeconds`, `HealthMetricWalkedDistanceMeters`, and `HealthActivityMask` changed. | | `HealthEventSleepUpdate` | `2` | Recent values around `HealthMetricSleepSeconds`, `HealthMetricSleepRestfulSeconds`, `HealthActivitySleep`, and `HealthActivityRestfulSleep` changed. | | `HealthEventHeartRateUpdate` | `4` | The value of `HealthMetricHeartRateBPM` has changed. | | `HealthEventHRVUpdate` | `5` | A new heart rate variability peak-to-peak interval reading is available. See the [_Heart Rate Monitor guide_](https://developer.repebble.com/guides/events-and-services/hrm.md#heart-rate-variability). | A simple example handler is shown below, which outputs to app logs the type of event that fired the callback: ``` static void health\_handler(HealthEventType event, void \*context) {// Which type of event occurred?switch(event) {case HealthEventSignificantUpdate:APP\_LOG(APP\_LOG\_LEVEL\_INFO,"New HealthService HealthEventSignificantUpdate event");break;case HealthEventMovementUpdate:APP\_LOG(APP\_LOG\_LEVEL\_INFO,"New HealthService HealthEventMovementUpdate event");break;case HealthEventSleepUpdate:APP\_LOG(APP\_LOG\_LEVEL\_INFO,"New HealthService HealthEventSleepUpdate event");break;case HealthEventHeartRateUpdate:APP\_LOG(APP\_LOG\_LEVEL\_INFO,"New HealthService HealthEventHeartRateUpdate event");break;}} ``` The subscription is then registered in the usual way, optionally providing a `context` parameter that is relayed to each event callback. The return value should be used to determine whether the subscription was successful: ``` #if defined(PBL\_HEALTH)// Attempt to subscribeif(!health\_service\_events\_subscribe(health\_handler, NULL)) {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Health not available!");}#elseAPP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Health not available!");#endif ``` ## Reading Health Data Health data is collected in the background as part of Pebble Health regardless of the state of the app using the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") API, and is available to apps through various [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") API functions. Before reading any health data, it is recommended to check that data is available for the desired time range, if applicable. In addition to the [`HealthServiceAccessibilityMask`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask "HealthServiceAccessibilityMask") value, health-related code can be conditionally compiled using `PBL_HEALTH`. For example, to check whether any data is available for a given time range: ``` #if defined(PBL\_HEALTH)// Use the step count metricHealthMetric metric = HealthMetricStepCount;// Create timestamps for midnight (the start time) and now (the end time)time\_t start = time\_start\_of\_today();time\_t end = time(NULL);// Check step data is availableHealthServiceAccessibilityMask mask = health\_service\_metric\_accessible(metric,start, end);bool any\_data\_available = mask & HealthServiceAccessibilityMaskAvailable;#else// Health data is not available herebool any\_data\_available = false;#endif ``` Most applications will want to read the sum of a metric for the current day's activity. This is the simplest method for accessing summaries of users' health data, and is shown in the example below: ``` HealthMetric metric = HealthMetricStepCount;time\_t start = time\_start\_of\_today();time\_t end = time(NULL);// Check the metric has data available for todayHealthServiceAccessibilityMask mask = health\_service\_metric\_accessible(metric,start, end);if(mask & HealthServiceAccessibilityMaskAvailable) {// Data is available!APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Steps today: %d",(int)health\_service\_sum\_today(metric));} else {// No data recorded yet todayAPP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Data unavailable!");} ``` For more specific data queries, the API also allows developers to request data records and sums of metrics from a specific time range. If data is available, it can be read as a sum of all values recorded between that time range. You can use the convenience constants from [`Time`](https://developer.repebble.com/docs/c/Standard_C/Time.md "Time"), such as [`SECONDS_PER_HOUR`](https://developer.repebble.com/docs/c/Standard_C/Time.md#SECONDS_PER_HOUR "SECONDS\_PER\_HOUR") to adjust a timestamp relative to the current moment returned by [`time()`](https://developer.repebble.com/docs/c/Standard_C/Time.md "Time"). > Note: The value returned will be an average since midnight, weighted for the length of the specified time range. This may change in the future. An example of this process is shown below: ``` // Make a timestamp for nowtime\_t end = time(NULL);// Make a timestamp for the last hour's worth of datatime\_t start = end - SECONDS\_PER\_HOUR;// Check data is availableHealthServiceAccessibilityMask result =health\_service\_metric\_accessible(HealthMetricStepCount, start, end);if(result & HealthServiceAccessibilityMaskAvailable) {// Data is available! Read itHealthValue steps = health\_service\_sum(HealthMetricStepCount, start, end);APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Steps in the last hour: %d", (int)steps);} else {APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "No data available!");} ``` ## Representing Health Data Depending on the locale of the user, the conventional measurement system used to represent distances may vary between metric and imperial. For this reason it is recommended to query the user's preferred [`MeasurementSystem`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystem "MeasurementSystem") before formatting distance data from the [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService"): > Note: This API is currently only meaningful when querying the [`HealthMetricWalkedDistanceMeters`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricWalkedDistanceMeters "HealthMetricWalkedDistanceMeters") metric. [`MeasurementSystemUnknown`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystemUnknown "MeasurementSystemUnknown") will be returned for all other queries. ``` const HealthMetric metric = HealthMetricWalkedDistanceMeters;const HealthValue distance = health\_service\_sum\_today(metric);// Get the preferred measurement systemMeasurementSystem system = health\_service\_get\_measurement\_system\_for\_display(metric);// Format accordinglystatic char s\_buffer[32];switch(system) {case MeasurementSystemMetric:snprintf(s\_buffer, sizeof(s\_buffer), "Walked %d meters", (int)distance);break;case MeasurementSystemImperial: {// Convert to imperial firstint feet = (int)((float)distance \* 3.28F);snprintf(s\_buffer, sizeof(s\_buffer), "Walked %d feet", (int)feet);} break;case MeasurementSystemUnknown:default:APP\_LOG(APP\_LOG\_LEVEL\_INFO, "MeasurementSystem unknown or does not apply");}// Display to user in correct unitstext\_layer\_set\_text(s\_some\_layer, s\_buffer); ``` ## Obtaining Averaged Data The [`HealthService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") also allows developers to read average values of a particular [`HealthMetric`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric "HealthMetric") with varying degrees of scope. This is useful for apps that wish to display an average value (e.g.: as a goal for the user) alongside a summed value. In this context, the `start` and `end` parameters specify the time period to be used for the daily average calculation. For example, a start time of midnight and an end time ten hours later will return the average value for the specified metric measured until 10 AM on average across the days specified by the scope. The [`HealthServiceTimeScope`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope "HealthServiceTimeScope") specified when querying for averaged data over a given time range determines how the average is calculated, as detailed in the table below: | Scope Type | Description | | --- | --- | | `HealthServiceTimeScopeOnce` | No average computed. The result is the same as calling [`health_service_sum()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum "health\_service\_sum"). | | `HealthServiceTimeScopeWeekly` | Compute average using the same day from each week (up to four weeks). For example, every Monday if the provided time range falls on a Monday. | | `HealthServiceTimeScopeDailyWeekdayOrWeekend` | Compute average using either weekdays (Monday to Friday) or weekends (Saturday and Sunday), depending on which day the provided time range falls. | | `HealthServiceTimeScopeDaily` | Compute average across all days of the week. | > Note: If the difference between the start and end times is greater than one day, an average will be returned that takes both days into account. Similarly, if the time range crosses between scopes (such as including weekend days and weekdays with [`HealthServiceTimeScopeDailyWeekdayOrWeekend`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScopeDailyWeekdayOrWeekend "HealthServiceTimeScopeDailyWeekdayOrWeekend")), the start time will be used to determine which days are used. Reading averaged data values works in a similar way to reading sums. The example below shows how to read an average step count across all days of the week for a given time range: ``` // Define query parametersconst HealthMetric metric = HealthMetricStepCount;const HealthServiceTimeScope scope = HealthServiceTimeScopeDaily;// Use the average daily value from midnight to the current timeconst time\_t start = time\_start\_of\_today();const time\_t end = time(NULL);// Check that an averaged value is accessibleHealthServiceAccessibilityMask mask =health\_service\_metric\_averaged\_accessible(metric, start, end, scope);if(mask & HealthServiceAccessibilityMaskAvailable) {// Average is available, read itHealthValue average = health\_service\_sum\_averaged(metric, start, end, scope);APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Average step count: %d steps", (int)average);} ``` ## Detecting Activities It is possible to detect when the user is sleeping using a [`HealthActivityMask`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask "HealthActivityMask") value. A useful application of this information could be to disable a watchface's animations or tick at a reduced rate once the user is asleep. This is done by checking certain bits of the returned value: ``` // Get an activities maskHealthActivityMask activities = health\_service\_peek\_current\_activities();// Determine which bits are set, and hence which activity is activeif(activities & HealthActivitySleep) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "The user is sleeping.");} else if(activities & HealthActivityRestfulSleep) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "The user is sleeping peacefully.");} else {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "The user is not currently sleeping.");} ``` ## Read Per-Minute History The [`HealthMinuteData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMinuteData "HealthMinuteData") structure contains multiple types of activity-related data that are recorded in a minute-by-minute fashion. This style of data access is best suited to those applications requiring more granular detail (such as creating a new fitness algorithm). Up to seven days worth of data is available with this API. > See [_Notes on Minute-level Data_](#notes-on-minute-level-data) below for more information on minute-level data. The data items contained in the [`HealthMinuteData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMinuteData "HealthMinuteData") structure are summarized below: | Item | Type | Description | | --- | --- | --- | | `steps` | `uint8_t` | Number of steps taken in this minute. | | `orientation` | `uint8_t` | Quantized average orientation, encoding the x-y plane (the "yaw") in the lower 4 bits (360 degrees linearly mapped to 1 of 16 values) and the z axis (the "pitch") in the upper 4 bits. | | `vmc` | `uint16_t` | Vector Magnitude Counts (VMC). This is a measure of the total amount of movement seen by the watch. More vigorous movement yields higher VMC values. | | `is_invalid` | `bool` | `true` if the item doesn't represent actual data, and should be ignored. | | `heart_rate_bpm` | `uint8_t` | Heart rate in beats per minute (if available). | These data items can be obtained in the following manner, similar to obtaining a sum. ``` // Create an array to store dataconst uint32\_t max\_records = 60;HealthMinuteData \*minute\_data = (HealthMinuteData\*)malloc(max\_records \* sizeof(HealthMinuteData));// Make a timestamp for 15 minutes ago and an hour before thattime\_t end = time(NULL) - (15 \* SECONDS\_PER\_MINUTE);time\_t start = end - SECONDS\_PER\_HOUR;// Obtain the minute-by-minute recordsuint32\_t num\_records = health\_service\_get\_minute\_history(minute\_data,max\_records, &start, &end);APP\_LOG(APP\_LOG\_LEVEL\_INFO, "num\_records: %d", (int)num\_records);// Print the number of steps for each minutefor(uint32\_t i = 0; i \< num\_records; i++) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Item %d steps: %d", (int)i,(int)minute\_data[i].steps);} ``` Don't forget to free the array once the data is finished with: ``` // Free the arrayfree(minute\_data); ``` ### Notes on Minute-level Data Missing minute-level records can occur if the watch is reset, goes into low power (watch-only) mode due to critically low battery, or Pebble Health is disabled during the time period requested. [`health_service_get_minute_history()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_minute_history "health\_service\_get\_minute\_history") will return as many **consecutive** minute-level records that are available after the provided `start` timestamp, skipping any missing records until one is found. This API behavior enables one to easily continue reading data after a previous query encountered a missing minute. If there are some minutes with missing data, the API will return all available records up to the last available minute, and no further. Conversely, records returned will begin with the first available record after the provided `start` timestamp, skipping any missing records until one is found. This can be used to continue reading data after a previous query encountered a missing minute. The code snippet below shows an example function that packs a provided [`HealthMinuteData`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMinuteData "HealthMinuteData") array with all available values in a time range, up to an arbitrary maximum number. Any missing minutes are collapsed, so that as much data can be returned as is possible for the allocated array size and time range requested. > This example shows querying up to 60 records. More can be obtained, but this increases the heap allocation required as well as the time taken to process the query. ``` static uint32\_t get\_available\_records(HealthMinuteData \*array, time\_t query\_start,time\_t query\_end, uint32\_t max\_records) {time\_t next\_start = query\_start;time\_t next\_end = query\_end;uint32\_t num\_records\_found = 0;// Find more records until no more are returnedwhile (num\_records\_found \< max\_records) {int ask\_num\_records = max\_records - num\_records\_found;uint32\_t ret\_val = health\_service\_get\_minute\_history(&array[num\_records\_found],ask\_num\_records, &next\_start, &next\_end);if (ret\_val == 0) {// a 0 return value means no more data is availablereturn num\_records\_found;}num\_records\_found += ret\_val;next\_start = next\_end;next\_end = query\_end;}return num\_records\_found;}static void print\_last\_hours\_steps() {// Query for the last hour, max 60 minute-level records// (except the last 15 minutes)const time\_t query\_end = time(NULL) - (15 \* SECONDS\_PER\_MINUTE);const time\_t query\_start = query\_end - SECONDS\_PER\_HOUR;const uint32\_t max\_records = (query\_end - query\_start) / SECONDS\_PER\_MINUTE;HealthMinuteData \*data =(HealthMinuteData\*)malloc(max\_records \* sizeof(HealthMinuteData));// Populate the arraymax\_records = get\_available\_records(data, query\_start, query\_end, max\_records);// Print the resultsfor(uint32\_t i = 0; i \< max\_records; i++) {if(!data[i].is\_invalid) {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Record %d contains %d steps.", (int)i,(int)data[i].steps);} else {APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Record %d was not valid.", (int)i);}}free(data);} ``` --- # Persistent Storage Source: https://developer.repebble.com/guides/events-and-services/persistent-storage/ Developers can use the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API to persist multiple types of data between app launches, enabling apps to remember information previously entered by the user. A common use-case of this API is to enable the app to remember configuration data chosen in an app's configuration page, removing the tedious need to enter the information on the phone every time the watchapp is launched. Other use cases include to-to lists, stat trackers, game highscores etc. Read [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) for more information on implementing an app configuration page. ## Persistent Storage Model Every app is allocated a fixed amount of persistent storage space and can write values to storage using a key, similar to [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") dictionaries or the web `localStorage` API. To recall values, the app simply queries the API using the associated key. Keys are specified in the `uint32_t` type, and each value can have a size up to [`PERSIST_DATA_MAX_LENGTH`](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_DATA_MAX_LENGTH "PERSIST\_DATA\_MAX\_LENGTH") (currently 256 bytes). The total space depends on the firmware. Call [`persist_get_max_size()`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_get_max_size "persist\_get\_max\_size") to read the limit in bytes at runtime rather than assuming a value: ``` size\_t max\_bytes = persist\_get\_max\_size(); ``` On Pebble 2 Duo, Pebble Time 2 and Pebble Round 2 the limit is 1 MB per app. The storage file starts at 4 kB and grows as values are written. On aplite, basalt, chalk and diorite the SDK compiles [`persist_get_max_size()`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_get_max_size "persist\_get\_max\_size") to the constant 4096, the 4 kB limit documented for those platforms. [`persist_get_max_size()`](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_get_max_size "persist\_get\_max\_size") was added in SDK 4.9.172; with an older SDK, assume 4 kB. When an app is updated the values saved using the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API will be persisted, but if it is uninstalled they will be removed. Apps that make large use of the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API may experience small pauses due to underlying housekeeping operations. Therefore it is recommended to read and write values when an app is launching or exiting, or during a time the user is waiting for some other action to complete. ## Types of Data Values can be stored as boolean, integer, string, or arbitrary data structure types. Before retrieving a value, the app should check that it has been previously persisted. If it has not, a default value should be chosen as appropriate. ``` uint32\_t key = 0;int num\_items = 0;if (persist\_exists(key)) {// Read persisted valuenum\_items = persist\_read\_int(key);} else {// Choose a default valuenum\_items = 10;// Remember the default value until the user chooses their own valuepersist\_write\_int(key, num\_items);} ``` The API provides a 'read' and 'write' function for each of these types, with builtin data types retrieved through assignment, and complex ones into a buffer provided by the app. Examples of each are shown below. ### Booleans ``` uint32\_t key = 0;bool large\_font\_size = true; ``` ``` // Write a boolean valuepersist\_write\_bool(key, large\_font\_size); ``` ``` // Read the boolean valuebool large\_font\_size = persist\_read\_bool(key); ``` ### Integers ``` uint32\_t key = 1;int highscore = 432; ``` ``` // Write an integerpersist\_write\_int(key, highscore); ``` ``` // Read the integer valueint highscore = persist\_read\_int(key); ``` ### Strings ``` uint32\_t key = 2;char \*string = "Remember this!"; ``` ``` // Write the stringpersist\_write\_string(key, string); ``` ``` // Read the stringchar buffer[32];persist\_read\_string(key, buffer, sizeof(buffer)); ``` ### Data Structures ``` typedef struct {int a;int b;} Data;uint32\_t key = 3;Data data = (Data) {.a = 32,.b = 45}; ``` ``` // Write the data structurepersist\_write\_data(key, &data, sizeof(Data)); ``` ``` // Read the data structurepersist\_read\_data(key, &data, sizeof(Data)); ``` > Note: If a persisted data structure's field layout changes between app versions, the data read may no longer be compatible (see below). ## Versioning Persisted Data As already mentioned, automatic app updates will persist data between app versions. However, if the format of persisted data changes in a new app version (or keys change), developers should version their storage scheme and correctly handle version changes appropriately. One way to do this is to use an extra persisted integer as the storage scheme's version number. If the scheme changes, simply update the version number and migrate existing data as required. If old data cannot be migrated it should be deleted and replaced with fresh data in the correct scheme from the user. An example is shown below: ``` const uint32\_t storage\_version\_key = 786;const int current\_storage\_version = 2; ``` ``` // Store the current storage scheme version numberpersist\_write\_int(storage\_version\_key, current\_storage\_version); ``` In this example, data stored in a key of `12` is now stored in a key of `13` due to a new key being inserted higher up the list of key values. ``` // The scheme has changed, increment the version numberconst int current\_storage\_version = 3; ``` ``` static void migrate\_storage\_data() {// Check the last storage scheme version the app usedint last\_storage\_version = persist\_read\_int(storage\_version\_key);if (last\_storage\_version == current\_storage\_version) {// No migration necessaryreturn;}// Migrate dataswitch(last\_storage\_version) {case 0:// ...break;case 1:// ...break;case 2: {uint32\_t old\_highscore\_key = 12;uint32\_t new\_highscore\_key = 13;// Migrate to scheme version 3int highscore = persist\_read\_int(old\_highscore\_key);persist\_write\_int(new\_highscore\_key, highscore);// Delete old datapersist\_delete(old\_highscore\_key);break;}// Migration is complete, store the current storage scheme version numberpersist\_write\_int(storage\_version\_key, current\_storage\_version);} ``` ## Alternative Method In addition to the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API, data can also be persisted using the `localStorage` API in PebbleKit JS, and communicated with the watch over [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") when the app is lanched. However, this method uses more power and fails if the watch is not connected to the phone. --- # Speaker Source: https://developer.repebble.com/guides/events-and-services/speaker/ On hardware platforms with a built-in speaker, the Speaker API gives apps four different ways to make sound (see the platform support table in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md#platform-support) for which watches have a speaker): - A **one-shot tone** , for short beeps and confirmation sounds. - A **note sequence** , for monophonic melodies that the system synthesizes from a chosen waveform. - **Polyphonic tracks** , where up to four voices are mixed together, each optionally backed by a pitch-shifted PCM sample. - **PCM streaming** , for arbitrary audio that the app generates or decodes on the fly. All four paths share the same volume control, status reporting, and finished-callback mechanism, so an app can mix and match them as it needs to. ## Volume Every speaker call takes a volume argument in the range `0`–`100`. The global volume can also be changed at any time, including while audio is playing: ``` speaker\_set\_volume(60); ``` For note sequences, individual notes can override the global volume by setting their `velocity` field (`1`–`127`); a velocity of `0` means "use the global volume." ## Playing a One-Shot Tone The simplest path is `speaker_play_tone()`, which plays a single frequency with a chosen waveform for up to ten seconds. It's well suited to UI beeps, alarms, and confirmation sounds: ``` // Play a 440 Hz sine wave for 250 ms at 80% volumespeaker\_play\_tone(440, 250, 80, SpeakerWaveformSine); ``` The available waveforms are: | Waveform | Description | | --- | --- | | [`SpeakerWaveformSine`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformSine "SpeakerWaveformSine") | Smooth sine wave; the gentlest option. | | [`SpeakerWaveformSquare`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformSquare "SpeakerWaveformSquare") | Hollow, retro square wave. | | [`SpeakerWaveformTriangle`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformTriangle "SpeakerWaveformTriangle") | Brighter than a sine, softer than a square. | | [`SpeakerWaveformSawtooth`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformSawtooth "SpeakerWaveformSawtooth") | Buzzy, harmonically rich sawtooth. | `speaker_play_tone()` returns `true` if playback started, or `false` if the duration is out of range or the speaker is busy with a different request. ## Playing a Note Sequence For monophonic melodies, build an array of [`SpeakerNote`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerNote "SpeakerNote") values and hand it to `speaker_play_notes()`. Each note carries a MIDI note number, a waveform, a duration in milliseconds (capped at 10000), and an optional per-note velocity: ``` // A short C-major arpeggiostatic const SpeakerNote s\_arpeggio[] = {{ .midi\_note = 60, .waveform = SpeakerWaveformSine,.duration\_ms = 200 }, // C4{ .midi\_note = 64, .waveform = SpeakerWaveformSine,.duration\_ms = 200 }, // E4{ .midi\_note = 67, .waveform = SpeakerWaveformSine,.duration\_ms = 200 }, // G4{ .midi\_note = 72, .waveform = SpeakerWaveformTriangle, .duration\_ms = 400 }, // C5};speaker\_play\_notes(s\_arpeggio, ARRAY\_LENGTH(s\_arpeggio), 80); ``` A `midi_note` of `0` is treated as a rest of the given duration. MIDI note `60` is middle C (C4), and the standard MIDI note numbering applies. ## Playing Polyphonic Tracks `speaker_play_tracks()` mixes up to four [`SpeakerTrack`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerTrack "SpeakerTrack") voices together. Each track is its own monophonic note sequence, so four tracks together can play a four-voice harmony. Each track may also point at a [`SpeakerSample`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerSample "SpeakerSample"), which causes the synthesizer to pitch-shift that PCM sample to the requested note instead of generating a waveform: ``` static const SpeakerNote s\_melody[] = {{ .midi\_note = 72, .waveform = SpeakerWaveformSine, .duration\_ms = 250 },{ .midi\_note = 74, .waveform = SpeakerWaveformSine, .duration\_ms = 250 },{ .midi\_note = 76, .waveform = SpeakerWaveformSine, .duration\_ms = 500 },};static const SpeakerNote s\_bass[] = {{ .midi\_note = 48, .waveform = SpeakerWaveformTriangle, .duration\_ms = 500 },{ .midi\_note = 50, .waveform = SpeakerWaveformTriangle, .duration\_ms = 500 },};static const SpeakerTrack s\_tracks[] = {{ .notes = s\_melody, .num\_notes = ARRAY\_LENGTH(s\_melody) },{ .notes = s\_bass,.num\_notes = ARRAY\_LENGTH(s\_bass)},};speaker\_play\_tracks(s\_tracks, ARRAY\_LENGTH(s\_tracks), 80); ``` To use a sample instead of a synthesized waveform, fill in a [`SpeakerSample`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerSample "SpeakerSample") and point the track at it. The sample's `base_midi_note` specifies which note plays the sample at its original pitch; other notes in the track are produced by pitch-shifting the same sample. Setting `loop` to `true` lets a short sample sustain through longer notes: ``` static const SpeakerSample s\_kick = {.data= kick\_pcm\_data,.num\_bytes= sizeof(kick\_pcm\_data),.format= SpeakerPcmFormat\_16kHz\_16bit,.base\_midi\_note = 36,// C2.loop= false,};static const SpeakerTrack s\_drum\_track = {.notes= s\_drum\_notes,.num\_notes = ARRAY\_LENGTH(s\_drum\_notes),.sample= &s\_kick,}; ``` Pass between 1 and 4 tracks to `speaker_play_tracks()`. Tracks beyond the fourth are not supported. ## Streaming PCM For arbitrary audio - synthesised on the fly, decoded from a downloaded file, or generated procedurally - open a PCM stream, push bytes into it, and close it when done. Open a stream by choosing one of the supported PCM formats and a starting volume: ``` if (!speaker\_stream\_open(SpeakerPcmFormat\_16kHz\_16bit, 80)) {// Speaker was busy or unavailablereturn;} ``` The supported formats are all mono, signed PCM: | Format | Bytes / sample | Sample rate | | --- | --- | --- | | [`SpeakerPcmFormat_8kHz_8bit`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_8kHz_8bit "SpeakerPcmFormat\_8kHz\_8bit") | 1 | 8 kHz | | [`SpeakerPcmFormat_16kHz_8bit`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_16kHz_8bit "SpeakerPcmFormat\_16kHz\_8bit") | 1 | 16 kHz | | [`SpeakerPcmFormat_8kHz_16bit`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_8kHz_16bit "SpeakerPcmFormat\_8kHz\_16bit") | 2 (little-endian) | 8 kHz | | [`SpeakerPcmFormat_16kHz_16bit`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_16kHz_16bit "SpeakerPcmFormat\_16kHz\_16bit") | 2 (little-endian) | 16 kHz | Then push samples into the stream. `speaker_stream_write()` returns the number of bytes it actually accepted, which may be less than the amount requested if the internal buffer is full. Loop and retry the remainder: ``` const uint8\_t \*cursor = buffer;uint32\_t remaining = buffer\_size;while (remaining \> 0) {uint32\_t written = speaker\_stream\_write(cursor, remaining);cursor+= written;remaining -= written;if (written == 0) {// Buffer is full - yield briefly and try againpsleep(5);}} ``` When all of the audio has been written, close the stream. `speaker_stream_close()` drains anything still buffered before it stops the speaker, so it's safe to call as soon as the last byte has been written: ``` speaker\_stream\_close(); ``` ## Stopping Playback Early Any playback path can be stopped immediately with: ``` speaker\_stop(); ``` This applies equally to tones, note sequences, polyphonic tracks, and PCM streams. If a finish callback is registered, it will fire with the [`SpeakerFinishReasonStopped`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishReasonStopped "SpeakerFinishReasonStopped") reason. ## Knowing When Playback Ends To find out when playback finishes - naturally or otherwise - register a finish callback before starting playback: ``` static void speaker\_finished(SpeakerFinishReason reason, void \*ctx) {switch (reason) {case SpeakerFinishReasonDone:APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Playback completed");break;case SpeakerFinishReasonStopped:APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Stopped by the app");break;case SpeakerFinishReasonPreempted:APP\_LOG(APP\_LOG\_LEVEL\_INFO, "Preempted by the system");break;case SpeakerFinishReasonError:APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "Playback error");break;}}speaker\_set\_finish\_callback(speaker\_finished, NULL);speaker\_play\_notes(s\_arpeggio, ARRAY\_LENGTH(s\_arpeggio), 80); ``` The callback runs on the app task, so it's safe to update UI or kick off follow-up playback from inside it. The current state can also be polled at any time with `speaker_get_status()`, which returns one of: | Status | Meaning | | --- | --- | | [`SpeakerStatusIdle`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatusIdle "SpeakerStatusIdle") | Nothing is playing. | | [`SpeakerStatusPlaying`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatusPlaying "SpeakerStatusPlaying") | A tone, sequence, track set, or stream is actively playing. | | [`SpeakerStatusDraining`](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatusDraining "SpeakerStatusDraining") | Playback has stopped accepting new input and is finishing the buffered audio. | ## Detecting Speaker Support Not every platform has a built-in speaker. There are two ways to handle this: At compile time, the `PBL_SPEAKER` preprocessor define is present on platforms that have a speaker, so speaker-specific code can be excluded entirely from builds for platforms that don't: ``` #if defined(PBL\_SPEAKER)speaker\_play\_tone(440, 250, 80, SpeakerWaveformSine);#elsevibes\_short\_pulse();#endif ``` At runtime, `speaker_play_tone()`, `speaker_play_notes()`, `speaker_play_tracks()`, and `speaker_stream_open()` all return `false` on platforms without a speaker (or when the speaker is busy with another request), so apps that always call through the API can just check the return value and fall back to vibration or a visual cue. ## Battery Considerations Driving the speaker is one of the most power-hungry things an app can do. Long PCM streams in particular keep the audio hardware powered and the CPU busy moving samples into the buffer. Use the speaker for short, intentional sounds - confirmations, alarms, brief melodies - and prefer `speaker_play_tone()` or `speaker_play_notes()` over PCM streaming whenever a synthesized sound would do the job. --- # Touch Source: https://developer.repebble.com/guides/events-and-services/touch/ On hardware platforms with a touchscreen, the `TouchService` lets an app receive touchdown, lift-off, and position updates as the user moves their finger across the display. See the platform support table in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md#platform-support) for which watches have a touchscreen. This is the same low-level event stream the system itself uses, so apps can build draggable UI or free-form input on top of it. Apps that want gestures rather than raw touches can use the built-in [gesture recognizers](#gesture-recognizers), and apps that just want their menus to scroll by touch can opt in to [touch navigation](#touch-navigation) without handling any touch events at all. **Important** Touch input is currently **not supported in watchfaces**. While we work out how we want to expose it, touch is intentionally restricted to watchapps - it is easier to allow it later than to take it away once apps depend on it. For now, only use the `TouchService` from a watchapp. ## Detecting Touch Support A touchscreen is not present on every platform, and even when it is the user can disable touch input from _Settings → Display → Touch_. Apps should call [`touch_service_is_enabled()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#touch_service_is_enabled "touch\_service\_is\_enabled") before relying on touch input - typically from the window's `appear` handler - and gracefully degrade if it returns `false`: ``` static void main\_window\_appear(Window \*window) {if (!touch\_service\_is\_enabled()) {text\_layer\_set\_text(s\_status\_layer,"Touch is disabled. Enable it in Settings → Display.");return;}// Touch is available - subscribe and start the touch UItouch\_service\_subscribe(touch\_handler, NULL);} ``` `touch_service_is_enabled()` returns `false` on platforms without a touchscreen, so a single check covers both the "no hardware" and the "user-disabled" cases. For code that should only be compiled on platforms with a touchscreen at all - for example, an entire gesture recognizer that has no equivalent on button-only hardware - use the `PBL_TOUCH` compile-time define: ``` #if defined(PBL\_TOUCH)touch\_service\_subscribe(touch\_handler, NULL);#else// Fall back to a button-based UIwindow\_set\_click\_config\_provider(window, click\_config\_provider);#endif ``` ## Subscribing to Touch Events Touch events are delivered through a [`TouchServiceHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchServiceHandler "TouchServiceHandler") callback. The handler receives a pointer to a [`TouchEvent`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent "TouchEvent") describing what happened, and the context pointer that was registered with the subscription: ``` static void touch\_handler(const TouchEvent \*event, void \*context) {switch (event-\>type) {case TouchEvent\_Touchdown:APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Touchdown at %d, %d", event-\>x, event-\>y);break;case TouchEvent\_PositionUpdate:APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Move to %d, %d", event-\>x, event-\>y);break;case TouchEvent\_Liftoff:APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Liftoff at %d, %d", event-\>x, event-\>y);break;}} ``` Subscribing to the service powers on the touch sensor; it stays on as long as at least one app is subscribed and is automatically disabled again once the last subscriber drops: ``` // Receive touch eventstouch\_service\_subscribe(touch\_handler, NULL); ``` When the app no longer needs touch input - for example, when its main window disappears - unsubscribe: ``` touch\_service\_unsubscribe(); ``` ## Event Types The [`TouchEventType`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEventType "TouchEventType") field on each event identifies what the user just did: | Event Type | Description | | --- | --- | | [`TouchEvent_Touchdown`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_Touchdown "TouchEvent\_Touchdown") | The user has just placed a finger on the screen. `x` and `y` are the initial contact position. | | [`TouchEvent_PositionUpdate`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_PositionUpdate "TouchEvent\_PositionUpdate") | An existing touch has moved. `x` and `y` are the new position. | | [`TouchEvent_Liftoff`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_Liftoff "TouchEvent\_Liftoff") | The user has just lifted their finger. `x` and `y` are the final position before lift-off. | Coordinates are in the same screen-relative pixel space used everywhere else in the UI, so they can be passed directly to drawing routines or compared against [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") bounds. A typical touch interaction starts with a single `TouchEvent_Touchdown`, followed by zero or more `TouchEvent_PositionUpdate` events as the finger moves, and ends with a single `TouchEvent_Liftoff`. Apps that want to track gestures (taps, drags, swipes) can do this by hand - store the touchdown position, watch the position updates, and decide what happened on lift-off - but in most cases the built-in [gesture recognizers](#gesture-recognizers) below do that work already. Each [`TouchEvent`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent "TouchEvent") also carries a `non_navigational` flag. It is set on touches that arrive while no _interaction session_ is active (see [Touch Navigation](#touch-navigation) below) — the user is touching the screen without having woken the watch first. Raw subscribers still receive these events and can decide for themselves whether to honor them. ## Touch Navigation Since firmware 4.32, the system can translate touches into the button-based navigation model: tapping and swiping in a [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") scrolls it and activates rows, taps on an [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") are zoned into up/select/down button events, and [`ActionMenu`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md "ActionMenu") items activate on tap. As of firmware 4.33 this _touch navigation_ is enabled by default, and it is gated on an **interaction session** : the user must press a button or wake the watch with a gesture before touches navigate. This prevents accidental navigation from brushing against an idle watchface. Watchapps are **opted out** of touch navigation by default, so existing apps behave exactly as before. To let the system drive your menus and scroll views by touch, opt in once at startup: ``` app\_touch\_navigation\_enable(true); ``` With touch navigation enabled for the app, taps and swipes that don't hit a touch-aware widget are bridged to button click events, so a plain `click_config_provider` keeps working without any touch-specific code. If one particular [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") handles its own touch input - with a raw subscription or with recognizers - take it out of the touch bridge so the system doesn't consume its touches: ``` window\_set\_touch\_bridge\_disabled(window, true); ``` ## Gesture Recognizers Rather than tracking touchdown/move/lift-off sequences by hand, apps can create _recognizers_ that watch the touch stream for one specific gesture and report progress through a callback. Three recognizers are available: | Recognizer | Constructor | Recognizes | | --- | --- | --- | | Tap | [`tap_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_create "tap\_recognizer\_create") | A single tap. Read the location with [`tap_recognizer_get_tap_point()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_get_tap_point "tap\_recognizer\_get\_tap\_point"). | | Pan | [`pan_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_create "pan\_recognizer\_create") | A drag locked to one axis ([`PanAxis_Horizontal`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis_Horizontal "PanAxis\_Horizontal") or [`PanAxis_Vertical`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis_Vertical "PanAxis\_Vertical")). Read movement with [`pan_recognizer_get_total_delta()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_total_delta "pan\_recognizer\_get\_total\_delta"), [`pan_recognizer_get_delta_since_start()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_start "pan\_recognizer\_get\_delta\_since\_start"), [`pan_recognizer_get_delta_since_prev()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_prev "pan\_recognizer\_get\_delta\_since\_prev") and [`pan_recognizer_get_velocity()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_velocity "pan\_recognizer\_get\_velocity"). | | Swipe | [`swipe_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_create "swipe\_recognizer\_create") | A fast, straight flick in one of the directions in the given [`SwipeDirection`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection "SwipeDirection") mask. Read the result with [`swipe_recognizer_get_direction()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_direction "swipe\_recognizer\_get\_direction") and [`swipe_recognizer_get_velocity()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_velocity "swipe\_recognizer\_get\_velocity"). | Each recognizer reports [`RecognizerEvent_Started`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Started "RecognizerEvent\_Started"), [`RecognizerEvent_Updated`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Updated "RecognizerEvent\_Updated"), [`RecognizerEvent_Completed`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Completed "RecognizerEvent\_Completed") and [`RecognizerEvent_Cancelled`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Cancelled "RecognizerEvent\_Cancelled") events to its [`RecognizerEventCb`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEventCb "RecognizerEventCb"). Recognizers are attached to a window with [`window_attach_recognizer()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_attach_recognizer "window\_attach\_recognizer"). The window takes ownership and destroys attached recognizers when it unloads, so in the common case there is nothing to clean up manually ([`recognizer_destroy()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_destroy "recognizer\_destroy") exists for recognizers that were never attached). Remember to also disable the window's touch bridge, or the system will consume the touches before your recognizers see them. This example scrolls a custom [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") by finger — something a bare `ScrollLayer` does not do on its own: ``` static ScrollLayer \*s\_scroll;static int16\_t s\_base;// content offset committed on Completestatic void pan\_handler(const Recognizer \*recognizer, RecognizerEvent event) {switch (event) {case RecognizerEvent\_Updated: {// delta\_since\_start is (0, 0) at Start, so the content does not jumpGPoint d = pan\_recognizer\_get\_delta\_since\_start(recognizer);scroll\_layer\_set\_content\_offset(s\_scroll, GPoint(0, s\_base + d.y), false);break;}case RecognizerEvent\_Completed:// Commit the new offsets\_base = scroll\_layer\_get\_content\_offset(s\_scroll).y;break;case RecognizerEvent\_Cancelled:// Roll back to the last committed offsetscroll\_layer\_set\_content\_offset(s\_scroll, GPoint(0, s\_base), true);break;default:break;}}static void main\_window\_load(Window \*window) {Layer \*root = window\_get\_root\_layer(window);s\_scroll = scroll\_layer\_create(layer\_get\_bounds(root));scroll\_layer\_set\_content\_size(s\_scroll,GSize(layer\_get\_bounds(root).size.w, total\_content\_height));// Add custom row layers as children of s\_scroll herelayer\_add\_child(root, scroll\_layer\_get\_layer(s\_scroll));// Handle this window's touches ourselves instead of the system bridgewindow\_set\_touch\_bridge\_disabled(window, true);// The window owns the recognizer and destroys it when the window unloadsRecognizer \*pan = pan\_recognizer\_create(pan\_handler, NULL, PanAxis\_Vertical);window\_attach\_recognizer(window, pan);} ``` When several recognizers watch the same window, they are evaluated exclusively by default: the first one to recognize its gesture wins. Use [`recognizer_set_simultaneous_with()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_simultaneous_with "recognizer\_set\_simultaneous\_with") to allow two recognizers to evaluate at the same time, or [`recognizer_set_fail_after()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_fail_after "recognizer\_set\_fail\_after") to hold one back until another has failed (e.g. only treat a touch as a tap once the swipe recognizer has given up). Note that on touch hardware a [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") already scrolls and activates by touch when the app has opted in to touch navigation. Recognizers are for interactions the built-in widgets don't provide, such as custom menus, drags, or acting on raw taps and swipes. ## Backlight Behavior Each touch event triggers the backlight the same way a wrist flick or button press would - the light flashes on for the system auto-off interval and then fades out. This keeps the screen lit naturally while the user is actively interacting, without keeping the backlight pinned on between taps. There is no need to call the [Light API](https://developer.repebble.com/guides/events-and-services/light.md) manually to achieve this; subsequent touches will re-trigger the backlight on their own. ## Battery Considerations The touch sensor is an active component and draws power continuously while enabled. Subscribe to the touch service only while the app's UI actually needs touch input, and unsubscribe as soon as it doesn't - for example, in the window `disappear` handler, or when navigating to a screen that uses buttons instead. --- # Wakeups Source: https://developer.repebble.com/guides/events-and-services/wakeups/ The [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup") API allows developers to schedule an app launch in the future, even if the app itself is closed in the meantime. A wakeup event is scheduled in a similar manner to a [`Timer`](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer") with a future timestamp calculated beforehand. ## Calculating Timestamps To schedule a wakeup event, first determine the timestamp of the desired wakeup time as a `time_t` variable. Most uses of the [`Wakeup`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup") API will fall into three distinct scenarios discussed below. ### A Future Time Call [`time()`](https://developer.repebble.com/docs/c/Standard_C/Time.md "Time") and add the offset, measured in seconds. For example, for 30 minutes in the future: ``` // 30 minutes from nowtime\_t timestamp = time(NULL) + (30 \* SECONDS\_PER\_MINUTE); ``` ### A Specific Time Use [`clock_to_timestamp()`](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_to_timestamp "clock\_to\_timestamp") to obtain a `time_t` timestamp by specifying a day of the week and hours and minutes (in 24 hour format). For example, for the next occuring Monday at 5 PM: ``` // Next occuring monday at 17:00 time\_t timestamp = clock\_to\_timestamp(MONDAY, 17, 0); ``` ### Using a Timestamp Provided by a Web Service The timestamp will need to be translated using the [`getTimezoneOffset()`](http://www.w3schools.com/jsref/jsref_gettimezoneoffset.asp) method available in PebbleKit JS or with any timezone information given by the web service. ## Scheduling a Wakeup Once a `time_t` timestamp has been calculated, the wakeup event can be scheduled: ``` // Let the timestamp be 30 minutes from nowconst time\_t future\_timestamp = time() + (30 \* SECONDS\_PER\_MINUTE);// Choose a 'cookie' value representing the reason for the wakeupconst int cookie = 0;// If true, the user will be notified if they missed the wakeup // (i.e. their watch was off)const bool notify\_if\_missed = true;// Schedule wakeup eventWakeupId id = wakeup\_schedule(future\_timestamp, cookie, notify\_if\_missed);// Check the scheduling was successfulif(id \>= 0) {// Persist the ID so that a future launch can query itconst wakeup\_id\_key = 43;persist\_write\_int(wakeup\_id\_key, id);} ``` After scheduling a wakeup event it is possible to perform some interaction with it. For example, reading the timestamp for when the event will occur using the [`WakeupId`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId "WakeupId") with [`wakeup_query()`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_query "wakeup\_query"), and then perform simple arithmetic to get the time remaining: ``` // This will be set by wakeup\_query()time\_t wakeup\_timestamp = 0;// Is the wakeup still scheduled?if(wakeup\_query(id, &wakeup\_timestamp)) {// Get the time remainingint seconds\_remaining = wakeup\_timestamp - time(NULL);APP\_LOG(APP\_LOG\_LEVEL\_INFO, "%d seconds until wakeup", seconds\_remaining);} ``` To cancel a scheduled event, use the [`WakeupId`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId "WakeupId") obtained when it was scheduled: ``` // Cancel a wakeupwakeup\_cancel(id); ``` To cancel all scheduled wakeup events: ``` // Cancel all wakeupswakeup\_cancel\_all(); ``` ## Limitations There are three limitations that should be taken into account when using the Wakeup API: - There can be no more than 8 scheduled wakeup events per app at any one time. - Wakeup events cannot be scheduled within 30 seconds of the current time. - Wakeup events are given a one minute window either side of the wakeup time. In this time no app may schedule an event. The return [`StatusCode`](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode "StatusCode") of [`wakeup_schedule()`](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_schedule "wakeup\_schedule") should be checked to determine whether the scheduling of the new event should be reattempted. A negative value indicates that the wakeup could not be scheduled successfully. The possible [`StatusCode`](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode "StatusCode") values are detailed below: | StatusCode | Value | Description | | --- | --- | --- | | `E_RANGE` | `-8` | The wakeup event cannot be scheduled due to another event in that period. | | `E_INVALID_ARGUMENT` | `-4` | The time requested is in the past. | | `E_OUT_OF_RESOURCES` | `-7` | The application has already scheduled all 8 wakeup events. | | `E_INTERNAL` | `-3` | A system error occurred during scheduling. | ## Handling Wakeup Events A wakeup event can occur at two different times - when the app is closed, and when it is already launched and in the foreground. If the app is launched due to a previously scheduled wakeup event, check the [`AppLaunchReason`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppLaunchReason "AppLaunchReason") and load the app accordingly: ``` static void init() {if(launch\_reason() == APP\_LAUNCH\_WAKEUP) {// The app was started by a wakeup event.WakeupId id = 0;int32\_t reason = 0;// Get details and handle the event appropriatelywakeup\_get\_launch\_event(&id, &reason);}/\* other init code \*/} ``` If the app is expecting a wakeup to occur while it is open, use a subscription to the wakeup service to be notified of such events: ``` static void wakeup\_handler(WakeupId id, int32\_t reason) {// A wakeup event has occurred while the app was already open} ``` ``` // Subscribe to wakeup servicewakeup\_service\_subscribe(wakeup\_handler); ``` The two approaches can also be combined for a unified response to being woken up, not depenent on the state of the app: ``` // Get details of the wakeupwakeup\_get\_launch\_event(&id, &reason);// Manually handle using the handlerwakeup\_handler(id, reason); ``` --- # Creating Pins Source: https://developer.repebble.com/guides/pebble-timeline/pin-structure/ A timeline pin contains all the information required to be displayed on the watch, and is written in the JSON object format. It can contain basic information such as title and times, or more advanced data such as notifications, reminders, or actions that can be used out from the pin view. ## Pin Overview The table below details the pin object fields and their function within the object. Those marked in **bold** are required. | Field | Type | Function | | --- | --- | --- | | **`id`** | String (max. 64 chars) | Developer-implemented identifier for this pin event, which cannot be re-used. This means that any pin that was previously deleted cannot then be re-created with the same `id`. | | **`time`** | String ([ISO date-time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)) | The start time of the event the pin represents, such as the beginning of a meeting. | | `duration` | Integer number | The duration of the event the pin represents, in minutes. | | `createNotification` | [Notification object](#notification-object) | The notification shown when the event is first created. | | `updateNotification` | [Notification object](#notification-object) | The notification shown when the event is updated but already exists. | | **`layout`** | [Layout object](#layout-object) | Description of the values to populate the layout when the user views the pin. | | `reminders` | [Reminder object](#reminder-object) array (Max. 3) | Collection of event reminders to display before an event starts. | | `actions` | [Action object](#action-object) array | Collection of event actions that can be executed by the user. | ### Notification Object The notification objects used for `createNotification` and `updateNotification` contain only one common field: a Layout object describing the visual properties of the notification. The other field (`time`) is used only in an `updateNotification` object. The `createNotification` type does **not** require a `time` attribute. | Field | Type | Function | | --- | --- | --- | | `layout` | [Layout object](#layout-object) | The layout that will be used to display this notification. | | `time` | String (ISO date-time) | The new time of the pin update. | The `createNotification` is shown when the pin is first delivered to the watch. The `updateNotification` is shown when the pin already existed and is being updated on the watch. It will only be shown if the `updateNotification.time` is newer than the last `updateNotification.time` received by the watch. Using these fields, developers can build a great experience for their users when live updating pins. For example, when sending updates about a sports game the app could use the `createNotification` to tell the user that "The game has just been added to your timeline" and the `updateNotification` to tell them that "The game starting time has just been updated in your timeline.". ### Layout Object The Layout object is used to describe any message shown in a customizable layout. This includes a pin in the timeline, a notification, and also reminders. Developers can choose between different layout types and customize them with attributes. Required fields are shown in **bold**. Some layout types have additional required fields not shown below, but listed in their dedicated sections. Values for icon URIs can be found below under [Pin Icons](#pin-icons), although not all icons are available at all sizes. | Field | Type | Function | | --- | --- | --- | | **`type`** | String | The type of layout the pin will use. See [_Pin Layouts_](#pin-layouts) for a list of available types. | | `title` | String | The title of the pin when viewed. | | `subtitle` | String | Shorter subtitle for details. | | `body` | String | The body text of the pin. Maximum of 512 characters. | | `tinyIcon` | String | URI of the pin's tiny icon. | | `smallIcon` | String | URI of the pin's small icon. | | `largeIcon` | String | URI of the pin's large icon. | The following attributes are also available for all pin layout types **(excluding notifications and reminders)**. | Field | Type | Function | | --- | --- | --- | | `primaryColor` | String | Six-digit color hexadecimal string or case-insensitive SDK constant (e.g.: "665566" or "mintgreen"), describing the primary text color. | | `secondaryColor` | String | Similar to `primaryColor`, except applies to the layout's secondary-colored elements. | | `backgroundColor` | String | Similar to `primaryColor`, except applies to the layout's background color. | | `headings` | Array of Strings | List of section headings in this layout. The list must be less than 128 characters in length, including the underlying delimiters (one byte) between each item. Longer items will be truncated with an ellipsis ('...'). | | `paragraphs` | Array of Strings | List of paragraphs in this layout. **Must equal the number of `headings`**. The list must be less than 1024 characters in length, including the underlying delimiters (one byte) between each item. Longer items will be truncated with an ellipsis ('...'). | | `lastUpdated` | ISO date-time | Timestamp of when the pin’s data (e.g: weather forecast or sports score) was last updated. | ### Reminder Object Reminders are synchronized to the watch and will be shown at the precise time set in the reminder. They work even when Pebble is disconnected from the user's mobile phone. | Field | Type | Function | | --- | --- | --- | | `time` | String (ISO date-time) | The time the reminder is scheduled to be shown. | | `layout` | [Layout object](#layout-object) | The layout of the reminder. | ### Action Object | Field | Type | Function | | --- | --- | --- | | `title` | String | The name of the action that appears on the watch. | | `type` | String | The type of action this will execute. See [_Pin Actions_](#pin-actions) for a list of available actions. | ## Minimal Pin Example The example pin object shown below includes only the required fields for a generic pin. ``` {"id": "example-pin-generic-1","time": "2015-03-19T18:00:00Z","layout": {"type": "genericPin","title": "News at 6 o'clock","tinyIcon": "system://images/NOTIFICATION\_FLAG"}} ``` ## Complete Pin Example Below is a more advanced example pin object: ``` {"id": "meeting-453923","time": "2015-03-19T15:00:00Z","duration": 60,"createNotification": {"layout": {"type": "genericNotification","title": "New Item","tinyIcon": "system://images/NOTIFICATION\_FLAG","body": "A new appointment has been added to your calendar at 4pm."}},"updateNotification": {"time": "2015-03-19T16:00:00Z","layout": {"type": "genericNotification","tinyIcon": "system://images/NOTIFICATION\_FLAG","title": "Reminder","body": "The meeting has been rescheduled to 4pm."}},"layout": {"title": "Client Meeting","type": "genericPin","tinyIcon": "system://images/TIMELINE\_CALENDAR","body": "Meeting in Kepler at 4:00pm. Topic: discuss pizza toppings for party."},"reminders": [{"time": "2015-03-19T14:45:00Z","layout": {"type": "genericReminder","tinyIcon": "system://images/TIMELINE\_CALENDAR","title": "Meeting in 15 minutes"}},{"time": "2015-03-19T14:55:00Z","layout": {"type": "genericReminder","tinyIcon": "system://images/TIMELINE\_CALENDAR","title": "Meeting in 5 minutes"}}],"actions": [{"title": "View Schedule","type": "openWatchApp","launchCode": 15},{"title": "Show Directions","type": "openWatchApp","launchCode": 22}]} ``` ## View Modes When viewing pins in the timeline, they can be displayed in two different ways. | State | Preview | Details | | --- | --- | --- | | Selected | ![](https://developer.repebble.com/assets/images/guides/timeline/timeline-selected.png) | Three lines of text shown from the title, location and sender. | | Not selected | ![](https://developer.repebble.com/assets/images/guides/timeline/timeline-one-line.png) | Time, short title, and icon are shown. | ## Pin Icons The tables below detail the available icons provided by the system. Each icon can be used when pushing a pin in the following manner: ``` "layout": {"type": "genericNotification","title": "Example Pin","tinyIcon": "system://images/NOTIFICATION\_FLAG"} ``` ### Notifications | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/NOTIFICATION_GENERIC.svg) | `NOTIFICATION_GENERIC` | Generic notification | | ![](https://developer.repebble.com/assets/images/guides/timeline/NOTIFICATION_REMINDER.svg) | `NOTIFICATION_REMINDER` | Reminder notification | | ![](https://developer.repebble.com/assets/images/guides/timeline/NOTIFICATION_FLAG.svg) | `NOTIFICATION_FLAG` | Generic notification flag | | ![](https://developer.repebble.com/assets/images/guides/timeline/NOTIFICATION_LIGHTHOUSE.svg) | `NOTIFICATION_LIGHTHOUSE` | Generic lighthouse | ### Generic | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/GENERIC_EMAIL.svg) | `GENERIC_EMAIL` | Generic email | | ![](https://developer.repebble.com/assets/images/guides/timeline/GENERIC_SMS.svg) | `GENERIC_SMS` | Generic SMS icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/GENERIC_WARNING.svg) | `GENERIC_WARNING` | Generic warning icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/GENERIC_CONFIRMATION.svg) | `GENERIC_CONFIRMATION` | Generic confirmation icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/GENERIC_QUESTION.svg) | `GENERIC_QUESTION` | Generic question icon | ### Weather | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/PARTLY_CLOUDY.svg) | `PARTLY_CLOUDY` | Partly cloudy weather | | ![](https://developer.repebble.com/assets/images/guides/timeline/CLOUDY_DAY.svg) | `CLOUDY_DAY` | Cloudy weather | | ![](https://developer.repebble.com/assets/images/guides/timeline/LIGHT_SNOW.svg) | `LIGHT_SNOW` | Light snow weather | | ![](https://developer.repebble.com/assets/images/guides/timeline/LIGHT_RAIN.svg) | `LIGHT_RAIN` | Light rain weather | | ![](https://developer.repebble.com/assets/images/guides/timeline/HEAVY_RAIN.svg) | `HEAVY_RAIN` | Heavy rain weather icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/HEAVY_SNOW.svg) | `HEAVY_SNOW` | Heavy snow weather icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/TIMELINE_WEATHER.svg) | `TIMELINE_WEATHER` | Generic weather icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/TIMELINE_SUN.svg) | `TIMELINE_SUN` | Sunny weather icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/RAINING_AND_SNOWING.svg) | `RAINING_AND_SNOWING` | Raining and snowing weather icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/SUNRISE.svg) | `SUNRISE` | Sunrise weather icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/SUNSET.svg) | `SUNSET` | Sunset weather icon | ### Timeline | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/TIMELINE_MISSED_CALL.svg) | `TIMELINE_MISSED_CALL` | Generic missed call icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/TIMELINE_CALENDAR.svg) | `TIMELINE_CALENDAR` | Generic calendar event icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/TIMELINE_SPORTS.svg) | `TIMELINE_SPORTS` | Generic sports icon | ### Sports | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/TIMELINE_BASEBALL.svg) | `TIMELINE_BASEBALL` | Baseball sports icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/AMERICAN_FOOTBALL.svg) | `AMERICAN_FOOTBALL` | American football sports icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/BASKETBALL.svg) | `BASKETBALL` | Basketball sports icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/CRICKET_GAME.svg) | `CRICKET_GAME` | Cricket sports icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/SOCCER_GAME.svg) | `SOCCER_GAME` | Soccer sports icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/HOCKEY_GAME.svg) | `HOCKEY_GAME` | Hockey sports icon | ### Action Results | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/RESULT_DISMISSED.svg) | `RESULT_DISMISSED` | Dismissed event | | ![](https://developer.repebble.com/assets/images/guides/timeline/RESULT_DELETED.svg) | `RESULT_DELETED` | Deleted event | | ![](https://developer.repebble.com/assets/images/guides/timeline/RESULT_MUTE.svg) | `RESULT_MUTE` | Mute event | | ![](https://developer.repebble.com/assets/images/guides/timeline/RESULT_SENT.svg) | `RESULT_SENT` | Generic message sent event | | ![](https://developer.repebble.com/assets/images/guides/timeline/RESULT_FAILED.svg) | `RESULT_FAILED` | Generic failure event | ### Events | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/STOCKS_EVENT.svg) | `STOCKS_EVENT` | Stocks icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/MUSIC_EVENT.svg) | `MUSIC_EVENT` | Music event | | ![](https://developer.repebble.com/assets/images/guides/timeline/BIRTHDAY_EVENT.svg) | `BIRTHDAY_EVENT` | Birthday event | | ![](https://developer.repebble.com/assets/images/guides/timeline/NEWS_EVENT.svg) | `NEWS_EVENT` | Generic news story event | | ![](https://developer.repebble.com/assets/images/guides/timeline/SCHEDULED_EVENT.svg) | `SCHEDULED_EVENT` | Generic scheduled event | | ![](https://developer.repebble.com/assets/images/guides/timeline/MOVIE_EVENT.svg) | `MOVIE_EVENT` | Generic movie icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/NO_EVENTS.svg) | `NO_EVENTS` | No events icon | ### Miscellaneous | Preview | Name | Description | | --- | --- | --- | | ![](https://developer.repebble.com/assets/images/guides/timeline/PAY_BILL.svg) | `PAY_BILL` | Pay bill event | | ![](https://developer.repebble.com/assets/images/guides/timeline/HOTEL_RESERVATION.svg) | `HOTEL_RESERVATION` | Hotel event | | ![](https://developer.repebble.com/assets/images/guides/timeline/TIDE_IS_HIGH.svg) | `TIDE_IS_HIGH` | High tide event | | ![](https://developer.repebble.com/assets/images/guides/timeline/INCOMING_PHONE_CALL.svg) | `INCOMING_PHONE_CALL` | Incoming phone call event | | ![](https://developer.repebble.com/assets/images/guides/timeline/DURING_PHONE_CALL.svg) | `DURING_PHONE_CALL` | Phone call event | | ![](https://developer.repebble.com/assets/images/guides/timeline/DURING_PHONE_CALL_CENTERED.svg) | `DURING_PHONE_CALL_CENTERED` | Phone call event centered | | ![](https://developer.repebble.com/assets/images/guides/timeline/DISMISSED_PHONE_CALL.svg) | `DISMISSED_PHONE_CALL` | Phone call dismissed event | | ![](https://developer.repebble.com/assets/images/guides/timeline/CHECK_INTERNET_CONNECTION.svg) | `CHECK_INTERNET_CONNECTION` | Check Internet connection event | | ![](https://developer.repebble.com/assets/images/guides/timeline/GLUCOSE_MONITOR.svg) | `GLUCOSE_MONITOR` | Sensor monitor event | | ![](https://developer.repebble.com/assets/images/guides/timeline/ALARM_CLOCK.svg) | `ALARM_CLOCK` | Alarm clock event | | ![](https://developer.repebble.com/assets/images/guides/timeline/CAR_RENTAL.svg) | `CAR_RENTAL` | Generic car rental event | | ![](https://developer.repebble.com/assets/images/guides/timeline/DINNER_RESERVATION.svg) | `DINNER_RESERVATION` | Dinner reservation event | | ![](https://developer.repebble.com/assets/images/guides/timeline/RADIO_SHOW.svg) | `RADIO_SHOW` | Radio show event | | ![](https://developer.repebble.com/assets/images/guides/timeline/AUDIO_CASSETTE.svg) | `AUDIO_CASSETTE` | Audio cassette icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/SCHEDULED_FLIGHT.svg) | `SCHEDULED_FLIGHT` | Scheduled flight event | | ![](https://developer.repebble.com/assets/images/guides/timeline/REACHED_FITNESS_GOAL.svg) | `REACHED_FITNESS_GOAL` | Reached fitness goal event | | ![](https://developer.repebble.com/assets/images/guides/timeline/DAY_SEPARATOR.svg) | `DAY_SEPARATOR` | Day separator icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/WATCH_DISCONNECTED.svg) | `WATCH_DISCONNECTED` | Watch disconnected event | | ![](https://developer.repebble.com/assets/images/guides/timeline/TV_SHOW.svg) | `TV_SHOW` | Generic TV show icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/LOCATION.svg) | `LOCATION` | Generic location icon | | ![](https://developer.repebble.com/assets/images/guides/timeline/SETTINGS.svg) | `SETTINGS` | Generic settings icon | ### Custom Icons Custom icons were introduced in SDK 4.0. They allow you to use custom images for timeline pins, by utilizing the [_Published Media_](https://developer.repebble.com/guides/tools-and-resources/app-metadata.md#published-media)`name`. E.g. `app://images/*name*` ## Pin Layouts Developers can customize how pins, reminders and notifications are shown to the user using different layouts. The Pebble SDK includes layouts appropriate for a broad set of apps. Each layout has different customization options, called the layout attributes. Most layouts also offer the option of showing an icon, which must be one of the standard system provided icons, listed under [_Pin Icons_](#pin-icons) above. The sub-sections below detail the available layouts and the fields they will display. Required attributes are shown in **bold**. ### Generic Layout Generic layout for generic pins of no particular type. **Timeline view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/generic-pin~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-pin~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-pin~chalk.png) **Detail view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/generic-layout~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-layout~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-layout~chalk.png) **Standard Attributes** **`title`** , **`tinyIcon`** , `subtitle`, `body`. **Color Elements** | Layout Property | Applies To | | --- | --- | | `primaryColor` | Time, body | | `secondaryColor` | Title | | `backgroundColor` | Background | **Example JSON** ``` {"id": "pin-generic-1","time": "2015-09-22T16:30:00Z","layout": {"type": "genericPin","title": "This is a genericPin!","tinyIcon": "system://images/NOTIFICATION\_FLAG","primaryColor": "#FFFFFF","secondaryColor": "#666666","backgroundColor": "#5556FF"}} ``` ### Calendar Layout Standard layout for pins displaying calendar events. **Timeline view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/calendar-pin~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/calendar-pin~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/calendar-pin~chalk.png) **Detail view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/calendar-layout~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/calendar-layout~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/calendar-layout~chalk.png) **Standard Attributes** **`title`** , `body`. **Special Attributes** | Field | Type | Function | | --- | --- | --- | | `locationName` | String | Name of the location of this pin event. Used if `shortSubtitle` is not present on the list view, and always in the detail view. | **Color Elements** | Layout Property | Applies To | | --- | --- | | `primaryColor` | Times, body | | `secondaryColor` | Title | | `backgroundColor` | Background | **Example JSON** ``` {"id": "pin-calendar-1","time": "2015-03-18T15:45:00Z","duration": 60,"layout": {"type": "calendarPin","title": "Pin Layout Meeting","locationName": "Conf Room 1","body": "Discuss layout types with Design Team."}} ``` ### Sports Layout Generic layout for displaying sports game pins including team ranks, scores and records. **Timeline view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/sport-pin~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/sport-pin~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/sport-pin~chalk.png) **Detail view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/sport-layout~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/sport-layout~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/sport-layout~chalk.png) **Standard Attributes** **`title`** (name of the game), `subtitle` (friendly name of the period), `body` (game description), **`tinyIcon`** , `largeIcon`, `lastUpdated`. **Special Attributes** > Note: The `rankAway` and `rankHome` fields will be shown before the event begins, otherwise `scoreAway` and `scoreHome` will be shown. | Field | Type | Function | | --- | --- | --- | | `rankAway` | String (~2 characters) | The rank of the away team. | | `rankHome` | String (~2 characters) | The rank of the home team. | | `nameAway` | String (Max 4 characters) | Short name of the away team. | | `nameHome` | String (Max 4 characters) | Short name of the home team. | | `recordAway` | String (~5 characters) | Record of the away team (wins-losses). | | `recordHome` | String (~5 characters) | Record of the home team (wins-losses). | | `scoreAway` | String (~2 characters) | Score of the away team. | | `scoreHome` | String (~2 characters) | Score of the home team. | | `sportsGameState` | String | `in-game` for in game or post game, `pre-game` for pre game. | **Color Elements** | Layout Property | Applies To | | --- | --- | | `primaryColor` | Text body | | `secondaryColor` | Team names and scores | | `backgroundColor` | Background | **Example JSON** ``` {"id": "pin-sports-1","time": "2015-03-18T19:00:00Z","layout": {"type": "sportsPin","title": "Bulls at Bears","subtitle": "Halftime","body": "Game of the Century","tinyIcon": "system://images/AMERICAN\_FOOTBALL","largeIcon": "system://images/AMERICAN\_FOOTBALL","lastUpdated": "2015-03-18T18:45:00Z","rankAway": "03","rankHome": "08","nameAway": "POR","nameHome": "LAC","recordAway": "39-19","recordHome": "39-21","scoreAway": "54","scoreHome": "49","sportsGameState": "in-game"}} ``` ### Weather Layout Standard layout for pins displaying the weather. **Timeline view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/weather-pin~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/weather-pin~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/weather-pin~chalk.png) **Detail view** #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/weather-layout~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/weather-layout~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/weather-layout~chalk.png) **Standard Attributes** **`title`** (part of the day), **`tinyIcon`** , `largeIcon`, `body` (shortcast), `lastUpdated`. **Special Attributes** | Field | Type | Function | | --- | --- | --- | | `shortTitle` | String | Used instead of `title` in the main timeline view unless it is not specified. | | `subtitle` | String | Show high/low temperatures. Note: currently only numbers and the degree symbol (°) are supported. | | `shortSubtitle` | String | Used instead of `subtitle` in the main timeline view unless it is not specified. | | **`locationName`** | String | Name of the location of this pin event. | | `displayTime` | String | Use a value of 'pin' to display the pin's time in title of the detail view and description, or 'none' to not show the time. Defaults to 'pin' if not specified. | **Color Elements** | Layout Property | Applies To | | --- | --- | | `primaryColor` | All text | | `backgroundColor` | Background | **Example JSON** ``` {"id": "pin-weather-1","time": "2015-03-18T19:00:00Z","layout": {"type": "weatherPin","title": "Nice day","subtitle": "40/65","tinyIcon": "system://images/TIMELINE\_SUN","largeIcon": "system://images/TIMELINE\_SUN","locationName": "Palo Alto","body": "Sunny with a chance of rain.","lastUpdated": "2015-03-18T18:00:00Z"}} ``` ### Generic Reminder Generic layout for pin reminders, which can be set at various times before an event is due to occur to remind the user ahead of time. #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/generic-reminder~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-reminder~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-reminder~chalk.png) **Standard Attributes** **`title`** , **`tinyIcon`**. **Special Attributes** | Field | Type | Function | | --- | --- | --- | | `locationName` | String | Name of the location of this pin event. | **Example JSON** ``` {"id": "pin-generic-reminder-1","time": "2015-03-18T23:00:00Z","layout": {"type": "genericPin","title": "This is a genericPin!","subtitle": "With a reminder!.","tinyIcon": "system://images/NOTIFICATION\_FLAG"},"reminders": [{"time": "2015-03-18T22:55:00Z","layout": {"type": "genericReminder","title": "Reminder!","locationName": "Conf Rm 1","tinyIcon": "system://images/ALARM\_CLOCK"}}]} ``` ### Generic Notification Generic notification layout which can be used with `createNotification` and `updateNotification` to alert the user to a new pin being created on their timeline. #### aplite #### basalt #### chalk ![](https://developer.repebble.com/assets/images/guides/timeline/generic-notification-layout~aplite.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-notification-layout~basalt.png) ![](https://developer.repebble.com/assets/images/guides/timeline/generic-notification-layout~chalk.png) **Standard Attributes** **`title`** , **`tinyIcon`** , `body`. **Color Elements** | Layout Property | Applies To | | --- | --- | | `primaryColor` | Title | | `backgroundColor` | Banner background | **Example JSON** ``` {"id": "pin-generic-createmessage-1","time": "2015-04-30T23:45:00Z","layout": {"type": "genericPin","title": "This is a genericPin!","subtitle": "With a notification","tinyIcon": "system://images/NOTIFICATION\_FLAG"},"createNotification": {"layout": {"type": "genericNotification","title": "Notification!","tinyIcon": "system://images/NOTIFICATION\_FLAG","body": "A new genericPin has appeared!"}}} ``` ## Pin Actions Pins can be further customized by adding actions to them. This allows bi- directional interactivity for pin-based apps. These apps can have multiple actions associated with them, allowing different launch behavior depending on how the user interacts with the pin. The table below shows the available actions that can be added to a pin. Required attributes are shown in **bold**. | Action `type` | Description | Attributes | | --- | --- | --- | | `openWatchApp` | Launch the watchapp associated with this pin. The `launchCode` field of this action object will be passed to the watchapp and can be obtained with [`launch_get_args()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_get_args "launch\_get\_args"). | **`title`** , **`launchCode`**. | | `http` | Execute an HTTP request that invokes this action on the remote service. | See [_HTTP Actions_](#http-actions) for full attribute details. | ### Using a Launch Code Launch codes can be used to pass a single integer value from a specific timeline pin to the app associated with it when it is lauched from that pin. This mechanism allows the context to be given to the app to allow it to change behavior based on the action chosen. For example, a pin could have two actions associated with an app for making restaurant table reservations that allowed the user to cancel the reservation or review the restaurant. To set up these actions, add them to the pin when it is pushed to the timeline API. ``` "actions": [{"title": "Cancel Table","type": "openWatchApp","launchCode": 15},{"title": "Leave Rating","type": "openWatchApp","launchCode": 22}] ``` ### Reading the Launch Code When the user sees the pin and opens the action menu, they can select one of these actions which will launch the watchapp (as dictated by the `openWatchApp` pin action `type`). When the app launches, use [`launch_get_args()`](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_get_args "launch\_get\_args") to read the value of the `launchCode` associated with the chosen action, and react accordingly. An example is shown below; ``` if(launch\_reason() == APP\_LAUNCH\_TIMELINE\_ACTION) {uint32\_t arg = launch\_get\_args();switch(arg) {case LAUNCH\_ARG\_CANCEL:// Cancel table UI...break;case LAUNCH\_ARG\_REVIEW:// Leave a review UI...break;}} ``` ### HTTP Actions With the `http` pin action `type`, pins can include actions that carry out an arbitrary HTTP request. This makes it possible for a web service to be used purely by pushed pins with actions that respond to those events. The table below details the attributes of this type of pin action object. Items shown in **bold** are required. | Attribute | Type | Default | Description | | --- | --- | --- | --- | | **`title`** | String | _mandatory_ | The title of the action. | | **`url`** | String | _mandatory_ | The URL of the remote service to send the request to. | | `method` | String | `POST` | The request method, such as `GET`, `POST`, `PUT` or `DELETE`. | | `headers` | Object | `{}` | Dictionary of key-value pairs of headers (`Content-Type` is implied by using `bodyJSON`) as required by the remote service. | | `bodyText` | String | `''` | The data body of the request in String format. | | `bodyJSON` | Object | _unspecified_ | The data body of the request in JSON object format. | | `successText` | String | "Done!" | The string to display if the action is successful. | | `successIcon` | Pin Icon URL | `system://images/GENERIC_CONFIRMATION` | The icon to display if the action is successful. | | `failureText` | String | "Failed!" | The string to display if the action is unsuccessful. | | `failureIcon` | Pin Icon URL | `system://images/RESULT_FAILED` | The icon to display if the action is unsuccessful. | > Note: `bodyText` and `bodyJSON` are mutually exclusive fields (they cannot be used together in the same request). You should choose that which is most convenient for your implementation. > > Note: Do not include a body with HTTP methods that do not support one. This means that `bodyText` and `bodyJSON` cannot be used with `GET` or `DELETE` requests. The following is an example action, using the `http` action `type` to confirm attendance at a meeting managed by a fictitious meeting scheduling service. ``` "actions": [{"type": "http","title": "Confirm Meeting","url": "http://some-meeting-service.com/api/v1/meetings/46146717","method": "PUT","headers": {"X-Request-Source": "pebble-timeline","Content-Type": "application/x-www-form-urlencoded"},"bodyText": "type=confirm&value=1","successIcon": "system://images/GENERIC\_CONFIRMATION","successText": "Confirmed!"}] ``` Alternatively, pins can use the `bodyJSON` field to encode a JSON object. Include this data using the `bodyJSON` field. ``` "actions": [{"type": "http","title": "Confirm Meeting","url": "http://some-meeting-service.com/api/v1/meetings/46146717","method": "PUT","headers": {"X-Request-Source": "pebble-timeline"},"bodyJSON": {"type": "confirm","value": true},"successIcon": "system://images/GENERIC\_CONFIRMATION","successText": "Confirmed!"}] ``` ### Included Headers When using the `http` action, the request will also include the following additional headers. Developers can use these to personalize the timeline experience to each individual user. | Header Key | Value | | --- | --- | | `X-Pebble-Account-Token` | Same as [`Pebble.getAccountToken()`](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) | | `X-Pebble-Watch-Token` | Same as [`Pebble.getWatchToken()`](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) | ## Testing Pins It is also possible to push new timeline pins using the `pebble`[_Command Line Tool_](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md). Prepare your pin in a JSON file, such as `example-pin.json` shown below: ``` {"id": "pin-generic-1","time": "2015-03-18T15:45:00Z","layout": {"type": "genericPin","title": "This is a genericPin!","tinyIcon": "system://images/NOTIFICATION\_FLAG"}} ``` Push this pin to your emulator to preview how it will appear for users. ``` $ pebble insert-pin example-pin.json ``` The pin will appear as shown below: ![pin-preview](https://developer.repebble.com/assets/images/guides/timeline/generic-pin~basalt.png) It is possible to delete the pin in a similar manner, making sure the `id` is the same as the pin to be removed: ``` $ pebble delete-pin --id pin-generic-1 ``` --- # Local Pins Source: https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins/ PebbleKit JS can insert pins into the timeline directly from the phone. Local pins are created by the Pebble mobile app and synced to the watch, which means: - No timeline token, API key, or appstore listing is needed, so sideloaded apps can use them. - Pins only exist on the phone that created them - they are not shared with the user's other phones, and cannot be created while your app's JS is not running. > The Pebble mobile app does not support the timeline web API, so pins can no longer be pushed to users from a web server. Local pins are the only way to add pins to the timeline from an app. ## Inserting a Pin Pass a pin object (or a JSON string) in the format described in [_Creating Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md): ``` Pebble.insertTimelinePin({id: 'local-pin-1',time: new Date().toISOString(),layout: {type: 'genericPin',title: 'Local pin',tinyIcon: 'system://images/NOTIFICATION\_FLAG'}}); ``` The `id` is chosen by you and only needs to be unique within your app. Inserting a pin with an `id` that already exists updates that pin, rather than creating a second one. ## Deleting a Pin Pins are removed using the same `id` they were inserted with: ``` Pebble.deleteTimelinePin('local-pin-1'); ``` ## Existing Timeline Web API Code Apps written against the timeline web API do not need to be rewritten to keep working. Requests made from PebbleKit JS to `timeline-api.rebble.io` or `timeline-api.getpebble.com` under `/v1/user/pins` are intercepted by the phone and turned into local pins instead of being sent to a server - a `PUT` or `POST` inserts the pin in the request body, and a `DELETE` removes the pin named in the URL. The request completes with a `200` response and an empty body. Only requests made by your app's JS are intercepted. Pins your backend pushes to the timeline web API will never reach the watch. > Interception can be turned off by the user with the 'Emulate Timeline Webservice' setting in the Pebble mobile app, so new apps should call `Pebble.insertTimelinePin()` directly. ## Unsupported Fields Local pins use the pin format described in [_Creating Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md), with the following exceptions: | Field | Behavior | | --- | --- | | `createNotification` | Ignored - no notification is shown when the pin is created. | | `updateNotification` | Ignored - no notification is shown when the pin is updated. | | `actions` | Ignored. Every local pin gets a 'Remove' action. | | `primaryColor`, `secondaryColor`, `backgroundColor` | Ignored. | All other fields, including `duration` and `reminders`, are supported. --- # Pebble Timeline Source: https://developer.repebble.com/guides/pebble-timeline/ The Pebble timeline is a system-level display of chronological events that apps can insert data into to deliver user-specific data, events, notifications and reminders. These items are called pins and are accessible outside the running app, but are deeply associated with an app the user has installed on their watch. Every user can view their personal list of pins from the main watchface by pressing Up for the past and Down for the future. The Pebble mobile app inserts pins for weather, calendar events and missed calls, and apps insert their own pins, such as sports scores or news items, from PebbleKit JS. ## Contents - [**Creating Pins**](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) - How to create timeline pins with reminders, actions, and layouts. - [**Local Pins**](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) - How to insert pins into the timeline directly from PebbleKit JS, without a web server or timeline API keys. - [**Subscriptions and Tokens**](https://developer.repebble.com/guides/pebble-timeline/timeline-js.md) - The PebbleKit JS timeline subscription and token APIs, and what to use instead. ## Adding Pins Apps add pins from PebbleKit JS with `Pebble.insertTimelinePin()` and remove them with `Pebble.deleteTimelinePin()`. The Pebble mobile app creates the pin and syncs it to the watch. No timeline token, API key or appstore listing is needed, so sideloaded apps can add pins. See [_Local Pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) for the API and [_Creating Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) for the pin format. > Note: The timeline web API is no longer available. The Pebble mobile app does not sync pins from a server, and `Pebble.timelineSubscribe()`, `Pebble.timelineUnsubscribe()` and `Pebble.timelineSubscriptions()` do not work. Use [_local pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) instead. --- # Service Architecture Source: https://developer.repebble.com/guides/pebble-timeline/timeline-architecture/ > Note: The timeline web API is no longer available. The Pebble mobile app does not sync pins from a server. Use [_local pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) instead. Every item on the timeline is called a 'pin'. A pin can have information attached to it which is used to show it to the user, such as layout, title, start time and actions. When the user is viewing their timeline, they can use the information provided about the immediate past and future to make decisions about how they plan the rest of their day and how to respond to any missed notifications or events. The system and the Pebble mobile app populate the user's timeline with items such as calendar appointments, missed calls and weather. Apps add their own pins from PebbleKit JS. For example, a sports app could show a pin representing an upcoming match so the user can see how long remains until it starts. Pins are created on the phone and sent to the watch. There is no server in the path, so no timeline token, API key or appstore listing is needed. The pin format is described in [_Creating Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) and the PebbleKit JS API in [_Local Pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md). ## Architecture Overview ![diagram](https://developer.repebble.com/assets/images/guides/3.0/timeline-architecture.png) The diagram shows the original timeline service. The 'Pebble Timeline API' cloud service and the developer's server no longer exist; today pins travel only between the phone and the watch. ### Watch Timeline The watch stores the pins and shows them when the user presses Up for the past or Down for the future from the watchface. Alarm pins are inserted directly on the watch. Pins carry their own layout, reminders and actions, so the watch does not need to contact the phone to display them. ### Pebble Mobile App The Pebble mobile app inserts system pins for the user's upcoming calendar events, missed calls and weather, and syncs them to the watch. It also runs the PebbleKit JS component of each installed app and forwards the pins that component inserts. ## Sources of Pins ### System Pins Calendar, missed call and weather pins are created by the Pebble mobile app. Alarm pins are created by the watch. Apps cannot change these pins. ### Local Pins Apps insert pins from PebbleKit JS with `Pebble.insertTimelinePin()` and remove them with `Pebble.deleteTimelinePin()`. Each pin is created on the phone that runs the app's JavaScript, so pins are not shared with the user's other phones and are only inserted while the app's JavaScript is running. Developers can use PebbleKit JS to combine the user's preferences from a configuration page (detailed in [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md)) with their location and data from web services to decide which pins to insert. --- # Subscriptions and Tokens Source: https://developer.repebble.com/guides/pebble-timeline/timeline-js/ > Note: The timeline web API is no longer available. The Pebble mobile app does not sync pins from a server, and `Pebble.timelineSubscribe()`, `Pebble.timelineUnsubscribe()` and `Pebble.timelineSubscriptions()` do not work. Use [_local pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) instead. `Pebble.getTimelineToken()` still returns a token: the appstore token for an installed app, or a placeholder for a sideloaded app while the _Emulate Timeline Webservice_ setting is on (the default). The token cannot be used to push pins, since there is no server to push them to. See [_Using PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md#account-token) for the tokens that identify a user or a watch. Apps add pins from PebbleKit JS with `Pebble.insertTimelinePin()` and remove them with `Pebble.deleteTimelinePin()`. See [_Local Pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) for the API and [_Creating Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) for the pin format. --- # Pebble Plugins Preview Source: https://developer.repebble.com/guides/plugins/preview/ **Notice** This page is generated from [`plugins.md`](https://github.com/coredevices/mobileapp/blob/ec2156cc38a850e5458addba2a2fb8efa34396df/test-apps/plugins.md) in the [Pebble mobile app repository](https://github.com/coredevices/mobileapp) at commit [ec2156cc3](https://github.com/coredevices/mobileapp/commit/ec2156cc38a850e5458addba2a2fb8efa34396df). Relative paths refer to that directory. Changes belong in that repository. Plugins are nearly here (finally). A plugin is a small piece of JavaScript that runs on the phone and: - **provides data** : (_sources_) that any Pebble watchface/app (and soon Index) can read - **takes actions** : turn a light off, add a task, that a watchface/app (and soon Index) can trigger It has a generic API, designed to allow any type of data to be exposed, in multiple formats. This doc lives here while plugins are under active development, and will move once they're stable. Right now they're supported in the Pebble mobile app (v1.14.0), but are experimental and disabled by default. ### Why? - A watchface that shows the weather shouldn't need its own weather API and key. The Pebble app already fetches weather for the user's saved locations; as a plugin, any app can display that. - If you have an interesting data source (say, a blood sugar monitor, or next-bus data), users can show it on whichever watchface they like instead of the one design you made. - Plugins let us expose data the Pebble app has but developers couldn't get at before very easily: music, notifications, phone state, and more. - Actions let a watch (and, later, Index) control things like lights or tasks through a plugin, without every app integrating with every service. - Once Index can use plugins, adding a to-do list service (like the Todoist, TickTick and Notion demos) is just a plugin, with no change to the Pebble app. ## What can I try now? - Try the example plugins, watchface and app. - Build a watchface or app that uses plugin data. - Build a plugin. First, enable plugins in the mobile app (v1.14.0 or above): **Settings → Debug → Show debug options** , then **Use experimental plugins**. The demo apps below are installed automatically, and a **Plugins** tab appears in the Apps screen listing plugins and their settings pages. ### Try the demo apps and plugins Source for each is in this folder: - **`weather-face`** ("Plugin Demo: Weather Face"): a watchface built entirely from a weather plugin: any installed one (the built-in one, `met-weather`, or yours), picked on its settings page. - **`met-weather`** : weather for the phone's current location from MET Norway (api.met.no), an alternative to the built-in weather plugin. Shows `navigator.geolocation` and drawing condition images in JS. - **`plugin-test`** ("Plugin Demo: Dashboard"): a watchapp with eight tiles, each set from its settings page to any reading from any installed plugin. This shows what data is available through the example/built-in plugins. You can also configure taps to take actions, or make a tile a button for any action that takes no arguments (e.g. "Back", the default, goes to the previous app). A tile can also show the date, from the watch's own clock. - **`stocks`** : share prices for tickers you pick on its settings page. - **`hue`** : Control Philips Hue lights on your local bridge (pair from its settings page). - **`notion`** , **`ticktick`** , **`todoist`** : examples of plugins that sign in with OAuth. ### What can't I do yet? **Don't ship anything that uses/offers plugins to the app store yet.** The API is unstable and will change before plugins are released to everyone. We want your feedback on it first. See [Future](#future) for what's planned but not built yet. ## Concepts ### Sources and actions A **source** is something an app can read from a plugin, addressed in layers: ``` category home item room_lights the kind of thing instance "3" one particular room (its name property: "Living room") property brightness one reading of it shape numericValue -> { value: 64, unit: "%", min: 0, max: 100 } ``` You can subscribe to an _item_ and get every property of every instance of it, and updates when they change. Instances have a stable `instanceId`; `name` is the property to display for one. An **action** is something a plugin can do (`set_on`, `add_task`). It takes JSON arguments described by a JSON Schema in the manifest, and can say which sources it affects (`targets`), so a tile showing a light knows which actions apply to it. ### Categories Apps ask for a category and item, not a plugin (`weather/location`, not "AcmeWeather"). The host serves it from whichever installed plugin provides it. Category, item and property names are free-form, so a plugin can offer any kind of source or action. But swapping one plugin for another only works if plugins serving the same category use the same names. So if an existing category fits, like weather, match its item and property names rather than inventing your own. We'll publish the names/shapes for common categories. Examples now: | Category | Items | From | | --- | --- | --- | | `weather` | `location`, `hour` | built-in | | `calendar` | `event` | built-in | | `phone` | `phone_state` | built-in | | `music` | `track` | built-in (Android) | | `notifications` | `notification` | built-in (Android) | | `watch` | `switch_setting`, `number_setting`, `text_setting`, `app_setting` | built-in | | `home` | `home_lights`, `room_lights`, `light` | Hue demo | | `finance` | `stock` | Stocks demo | | `notes` | `destination`, `configured` | Notion demo | | `reminders` | `list`, `configured` | Todoist, TickTick demos | A plugin is offered as a notes / reminders destination for the Index agent when it declares a `create_note` / `create_reminder` action _and_ serves `notes/configured` / `reminders/configured`: one instance whose `configured` property is a `boolean` that is true once the user has signed in and set everything the action needs. While it reads false the destination shows a Connect button that opens the plugin's settings page. An action without its `configured` source is not offered. The built-in Watch State plugin has no sources, only actions, both needing `LaunchApps`: `launch_app` (`{ uuid }`) opens an app or watchface in the user's locker, and `launch_previous_app` goes back to whatever ran before the current app. ### Shapes A property isn't typed as "a number" or "a string". It offers one or more **shapes** : fixed types that a consuming app can be confident of being able to render. The plugin sends every shape it can, and you pick one. | Shape | For | Payload | | --- | --- | --- | | `shortText` | up to ~7 characters | `{ text: "72°F" }` | | `longText` | a line of text | `{ text: "Partly cloudy, breezy" }` | | `numericValue` | a number, with a range if it has one | `{ value: 84, unit: "%", min: 0, max: 100 }` | | `timestamp` | a moment, in epoch seconds UTC | `{ value: 1716940800 }` | | `boolean` | on/off state | `{ value: true }` | | `icon` | a monochrome glyph | `{ pixels, palette, width, height }` | | `image` | a colour bitmap | `{ pixels, palette, width, height }` | - Some shapes carry optional metadata (unit, min, max) so an app can render the data in context. - `icon` and `image` are 4-bpp palettised bitmaps (base64), only produced if you ask for them with `iconPixelSize`. A missing property or shape means the plugin can't serve it right now. Check before drawing. Shapes are designed so you can build for a _slot in your design_ rather than for specific data. A face with a gauge widget can offer the user anything that has a `numericValue` with a range: battery, UV index, a light's brightness. A small text slot can take any `shortText`, whatever it came from. An `icon` suits a small monochrome mark beside a reading, and an `image` suits a full-colour one. ### Permissions Plugins introduce a fine-grained permission system, covering your plugin and your app's PKJS. This ships in v1.15 of the mobile app, and is only in place when plugins are enabled. #### Declaring Declare what your app and its plugin need once, in the `pebble` block of `package.json`: ``` "pebble": {"usesPermissions": ["Location","LocalNetwork",{ "name": "Internet", "parameters": { "domains": ["api.example.com"] } }]} ``` The built-in permissions are `Internet`, `LocalNetwork`, `Location`, `Notifications`, `Calendar`, `Contacts`, `Timeline`, `ReadWatchSettings`/`WriteWatchSettings`, `ReadMusic`/`ControlMusic`, `LaunchApps`, `LegacyPebbleKit` and `Health`. - Each `Internet` domain is its own permission and covers its subdomains. `LocalNetwork` covers devices on the LAN. - `LegacyPebbleKit` lets your watchapp talk to a companion app over PebbleKit Classic. A PebbleKit 2 companion doesn't need declaring: each package your appinfo lists is asked about on its own. - `Health` isn't enforced yet: health data is read on the watch, so enforcing it needs permissions synced to the watch, which isn't built yet. Declare it anyway if you use health data. The [packaging script](#packaging-and-sideloading) marks the pbw as using permissions (`manifestVersion: 2` in its appinfo). Without that it's a legacy app, which keeps what apps had before permissions: internet access, and what its `capabilities` declare. Sources and actions guard themselves with **caller permissions** : what an app must hold to use them. `calendar/event` needs `Calendar`, `notifications/notification` needs `Notifications`, `weather/*` needs `Location`. A plugin can also define its own permissions, to guard its sources and actions: ``` "plugin": {"definesPermissions": [{ "name": "HomeControl", "label": "Control your lights", "kind": "write" }],"actions": [{ "name": "set\_on", "callerPermissions": ["HomeControl"], ... }]} ``` `kind` is `read`, `write` or `exfiltrating`, and decides when users are asked, as for a built-in permission. The name is scoped to your plugin, and users see your label with where it came from ("Control your lights (via Hue)"). Actions get one more check: if your plugin can send data off the phone, an app invoking its actions needs the user's permission for that too (`InvokeAction`), since the arguments go wherever your plugin can send them. To use another plugin, declare its permissions in your `usesPermissions` with its uuid, so users can approve them when installing your app (if that plugin is already installed): ``` "usesPermissions": [{ "name": "HomeControl", "plugin": "\" },{ "name": "InvokeAction", "plugin": "\" }] ``` #### Approval Network access and companion apps let data leave the phone, so they're what make reading someone's data risky. Those, and permissions that change things (`Timeline`, `WriteWatchSettings`, `ControlMusic`, `LaunchApps`), need the user's approval unless they've set apps to never ask. Reading is up to a setting; by default: - **An app that can send data off the phone** has the user approve each permission: the ones it declares when it's installed, and anything else (an undeclared permission, or a new domain) when it first uses it. - **Any other app** can read without asking, since the data can't go anywhere. While your app is running (or, for a plugin, while something reads from it), a request shows the user a notification and a card in the Pebble app. Until they answer, it fails with `PERMISSION_DENIED` (a blocked `fetch` rejects like a network error), possibly after waiting a few seconds. Try again later rather than treating it as permanent. Users can change any permission on the app's page, and choose in settings when apps have to ask. Declare what you need up front, and keep `Internet` to the domains you use: fewer prompts, and users can trust you. An app holding `Location` whose only network access is `api.openweathermap.org` can't send the user's location anywhere else. ## Writing a watchface or app that uses plugins Your app uses plugins from its PKJS (`pebble-js-app.js`). ### Three ways to use plugins 1. **You know exactly what you want.** Hard-code the category, item and properties, as the `weather-face` demo does with `weather/location`. Whichever weather plugin the user has, the face works. It uses the condition `image` the plugin provides - but a face that wants its own artwork could read the `condition_code` property instead (`sun`, `partly_cloudy`, `heavy_rain`, ...) and draw its own. 2. **Let the user choose.** Use `Pebble.enumeratePlugins()` to see everything available, and a settings page for the user to pick what goes where, as the `plugin-test` dashboard does. Filter by the shapes your design can draw (see [Shapes](#shapes)). 3. **Use what the user already prefers** _(not built yet)_. Users will pick their favourite readings in the Pebble app: e.g. a user might select weather for home location first, then phone battery, then the Google stock price. An API will expose that ordered list, so any face with widget slots can display those items with no configuration required. **Reading a source:** ``` const subscription = Pebble.subscribeToSource({category: "weather",item: "location",properties: ["name", "temperature", "condition"],// optional hint; omit for everythingiconPixelSize: { w: 48, h: 48 },// optional; ask only if you want bitmapsonData: ({ instances }) =\> {const here = instances[0];// first saved location (can be multiple if supportsMultiple==true)if (!here) return;const p = here.properties;send(p.name.shortText.text, p.temperature.shortText.text);},onError: (err) =\> console.log(err.code, err.message),});subscription.unsubscribe();// or let it end with the app ``` Every update is an envelope: ``` {pluginUuid: "...",validUntilMs: 1716940000000,instances: [{instanceId: "3",properties: {name:{ shortText: { text: "Kitchen" } },brightness: { numericValue: { value: 64, unit: "%", min: 0, max: 100 },shortText:{ text: "64%" } },},},],} ``` **Reading from a specific plugin:** pass `plugin: ""`, for example your own pbw's plugin. This is a preference. If that plugin isn't installed or doesn't serve the item, another plugin serving it is used. Check `pluginUuid` on the envelope if you need to know which one answered. **Invoking an action** : `plugin` is required here, and never falls back to another plugin: ``` const result = await Pebble.invokeAction({plugin: "\",action: "set\_on",args: { item: "light", instanceId: "3", on: false },});// { ok: true, text: "Kitchen off.", refreshed: ["home/light"] }// { ok: false, code: "AUTH\_REQUIRED", message: "..." } ``` **Discovering what's installed:** `Pebble.enumeratePlugins()` returns every installed plugin with its sources (items, properties and shapes) and actions. Error codes: `PLUGIN_UNAVAILABLE`, `PERMISSION_DENIED`, `RATE_LIMITED`, `TIMEOUT`, `INVALID_REQUEST`, `UNKNOWN`, plus `AUTH_REQUIRED` and `INVALID_ARGS` for actions. ## Writing a plugin A plugin ships in a `.pbw`: either alongside a watchapp/watchface, or on its own as a **plugin-only** pbw (no watch binary). It uses the pbw's uuid and name. The simplest example to copy is `stocks/`. **If your app fetches data from a service and displays it, consider splitting it into a plugin plus the app, in one pbw.** The app reads from its own plugin (pass your pbw's uuid as `plugin`), and behaves the same as it does now. But the data is also available to every other watchface and app, and later to Index - and users can show it in a design other than yours. ### The manifest Add a `plugin` block to the `pebble` section of `package.json`: ``` "pebble": {"displayName": "Stocks","uuid": "2c4b7f10-9a3d-4e6b-8f21-5d0c7e9a1b34","sdkVersion": "3","configPage": "config.html",// optional settings page (a file in the pbw, or a URL)"usesPermissions": [{ "name": "Internet", "parameters": { "domains": ["query1.finance.yahoo.com"] } }],"plugin": {"description": "Share prices for the tickers the user picked.",// also read by index MCP"script": "plugin.js","sources": [{"category": "finance","items": ["stock"],"properties": {"name":["shortText"],"price": ["shortText", "longText", "numericValue"]},"supportsMultiple": true,"suggestedRefreshIntervalSec": 60}],"actions": [{"name": "add\_stock",// illustrative; the real Stocks plugin has no actions"description": "Start tracking a ticker symbol.","parameters": {// JSON Schema"type": "object","properties": { "symbol": { "type": "string" } },"required": ["symbol"]},"targets": ["finance/stock"]}]}} ``` ### The script A plugin only runs when it's needed: for a subscription, an action, or while its settings page is open. It doesn't stay running in the background. While an app is subscribed, the plugin is called regularly for fresh data (currently every `suggestedRefreshIntervalSec`, but this may change). ``` // plugin.jsPebble.registerSourceHandler(async (request, respond) =\> {// request: { category, item, properties?, iconPixelSize? }if (request.category === "finance" && request.item === "stock") {const quotes = await loadQuotes();respond.data({validUntilMs: Date.now() + 60\_000,instances: quotes.map((q) =\> ({instanceId: q.symbol,properties: {name:{ shortText: { text: q.name } },price: { shortText: { text: `$${q.price}` }, numericValue: { value: q.price, unit: "$" } },},})),});return;}respond.error("PLUGIN\_UNAVAILABLE");});Pebble.registerActionHandler(async (request, respond) =\> {// request: { action, args }try {await addStock(request.args.symbol);respond.ok({ text: "Added.", refreshed: ["finance/stock"] });} catch (e) {respond.error("UNKNOWN", e.message);}}); ``` What a plugin has to work with: - **`fetch`** for HTTP, to hosts the user has allowed (see [Approval](#approval)). A blocked host rejects like a network error. Plain-HTTP LAN devices are reachable with `LocalNetwork`. - **`WebSocket`** under the same rules: a blocked host fires `error` then `close`. Binary frames arrive as `ArrayBuffer`. Sockets are closed when the session ends, so one only lives for the request that opened it. These aren't intended for long-running connections. - **`navigator.geolocation`** (`getCurrentPosition`, `watchPosition`, `clearWatch`), as in PKJS. Needs `Location`; refused, it calls your error callback. Watches end with the session. - **`localStorage`** for state, shared with your app's PKJS. A plugin is started fresh for each request, so don't keep state in variables. - **`Pebble.secureStorage`** for credentials (tokens, API keys): `await getItem(key)`, `setItem(key, value)` and `removeItem(key)`, encrypted by the phone's keystore. Private to your plugin (PKJS can't read it) and deleted when the app is removed. - **`Pebble.refreshSources(["finance/stock"])`** when data changes outside a request (for example, the user added a ticker on the settings page). - **`Pebble.oauth`** for sign-in with OAuth providers, without the plugin ever holding a client secret. Declare the connector in the manifest (`"oauth": { "todoist": {} }`), then `await Pebble.oauth.authorize("todoist")` for a token and `Pebble.oauth.refresh("todoist", broker_refresh)` to renew it; keep both in `Pebble.secureStorage`. The connector has to be registered with the Pebble app store (see the `todoist/` example). - Nothing else: unlike PKJS, a plugin has no WebView APIs. Return every property and shape you can; text shapes cost nothing once you have the data, and the app picks. Only produce `icon`/`image` when the request has `iconPixelSize`. ### Settings pages Until now, a PKJS settings page had to be hosted somewhere (or generated with Clay), and could only pass data through its URL + when it closed. Settings pages can now be bundled in the pbw, and can message your plugin/PKJS code in real-time while it's open. Set `configPage` in `package.json` to an HTML file in the project (bundled into the pbw) or a URL. If it's set, it's used instead of the legacy `showConfiguration` flow. ``` // in the pagePebble.addEventListener("ready", (e) =\> { /\* e.target ('plugin' or 'pkjs') is up \*/ });const reply = await Pebble.sendMessage("plugin", { type: "listTickers" });// in plugin.jsPebble.registerConfigHandler((message, respond) =\> respond({ tickers: tickers() }));// in pebble-js-app.jsPebble.addEventListener("configmessage", (e) =\> e.respond({ theme: theme() })); ``` Wait for `ready` before sending: the plugin is started when the page opens. The `plugin-test` dashboard shows what this makes possible: its settings page asks the app's PKJS for the catalogue of everything installed (built from `enumeratePlugins`) to fill its dropdowns. Each change is sent straight to PKJS, so the watch updates as the user edits, with no save button. PKJS pushes the live readings back, so each option is labelled with what it currently reads (for example "temperature: 21°C"), and each tile shows its current value. ### Packaging and sideloading The standard `pebble build` doesn't put `plugin.js` or `config.html` in the pbw (or support plugin-only PBWs), so use [`scripts/pack-plugin-pbw.py`](https://github.com/coredevices/mobileapp/blob/ec2156cc38a850e5458addba2a2fb8efa34396df/scripts/pack-plugin-pbw.py): ``` scripts/pack-plugin-pbw.py path/to/my-plugin ``` It works out what to do from the project. If there's a watchapp (`src/c`), it runs `pebble build`, then adds the plugin files. Otherwise it builds a plugin-only pbw directly. Either way it marks the pbw as using [permissions](#declaring). Sideloading plugins is supported from v1.14.0 of the mobile app - but the manifest format changed in v1.15.0 (along with several other parts of the API). ## Future We're planning: - Index integration (plugins as MCP tools). - Tooling support (the Pebble tool, CloudPebble). - Permission metadata in the app store, so users can review an app's permissions before adding it. - Choosing favourite readings in the app, and a preferred plugin when several serve the same thing (e.g. weather), plus an API to read those choices. - More extensive documentation. - A watch-side (C / Alloy) API, so apps can use plugins without any phone-side JS. - A way to cache images on the watch (see the Weather demo face which needs a phone connection to display images after restarting, right now) - PebbleKit-Android plugin support. - Plugins calling other plugins. - App store support, including a Plugins section for discovery. - More built-in plugins exposing more things. --- # Plugins (Preview) Source: https://developer.repebble.com/guides/plugins/ A plugin is JavaScript in a `.pbw` that runs on the phone, inside the Pebble mobile app, and offers two things to other apps: _sources_, data that any watchface or watchapp can read, and _actions_, operations such as turning a light off or adding a task that any watchapp can trigger. A watchface that shows the weather no longer needs its own weather API and key; it subscribes to `weather/location` and whichever weather plugin the user has installed answers. The Pebble mobile app exposes some of its own data as built-in plugins, and the [Index 01](https://developer.repebble.com/index-01.md) assistant will call plugin actions as tools. **Important** Plugins are a preview. The API will change before plugins are released to all users, and the Pebble mobile app ships with them turned off. Do not publish apps that use or provide plugins to the appstore yet. The documentation is maintained alongside the implementation and the example plugins in the [Pebble mobile app repository](https://github.com/coredevices/mobileapp/tree/master/test-apps), and is reproduced here. ## Contents - [**Pebble Plugins Preview**](https://developer.repebble.com/guides/plugins/preview.md) - What plugins are, how to try the preview in the Pebble mobile app, how a watchapp reads plugin data from PebbleKit JS, and how to write a plugin. --- # Building with AI Coding Agents Source: https://developer.repebble.com/guides/ai-agents/ AI coding agents such as Claude Code, Cursor and Codex can write Pebble apps when they can read the SDK documentation, know how the project is set up and can run the result in the emulator. This guide describes what the developer site and the `pebble` tool provide for each of these. ## Building a Watchface with an Agent The quickest way to see what an agent can do is to point it at the [Pebble agent skill](#the-pebble-agent-skill) and ask for a watchface. With the Pebble SDK installed, start Claude Code, Codex or Cursor in an empty directory and use a prompt like this: ``` Install the Pebble agent skill from https://github.com/coredevices/pebble-watchface-agent-skill (follow its README), then use it to build a watchface for Pebble Time 2 that shows the time in large digits with the date underneath. Build it, install it in the emulator and show me a screenshot. ``` The agent clones the skill, copies it into the project, then creates the watchface, runs `pebble build`, installs the result in the emulator with `pebble install --emulator emery`, takes a screenshot with `pebble screenshot` and compares it with the request before reporting back. Ask for changes in the same session ("make the digits bold", "add a battery meter") and it repeats the build and screenshot. To install on a watch, see [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md). ## Documentation for Agents Most guide and reference pages on this site have a Markdown version. Replace the trailing `/` or `/index.html` of the page URL with `.md`. For example, the Markdown version of `https://developer.repebble.com/guides/events-and-services/buttons/` is `https://developer.repebble.com/guides/events-and-services/buttons.md`. The Markdown version has no navigation or scripts, and its links point to the Markdown versions of the other pages. [`/llms.txt`](https://developer.repebble.com/llms.txt) is an index of every page with a one-line description, grouped by section. It starts with a short block that states the current SDK version, the target platforms and what the SDK no longer supports. Give an agent this URL and tell it to read the `.md` version of any page it needs. Larger files are available for agents that read a whole section at once: | File | Contents | | --- | --- | | [`/llms-full.txt`](https://developer.repebble.com/llms-full.txt) | Every page | | [`/guides/llms-full.txt`](https://developer.repebble.com/guides/llms-full.txt) | All guides | | [`/guides/alloy/llms-full.txt`](https://developer.repebble.com/guides/alloy/llms-full.txt) | The Alloy guides | | [`/docs/c/llms-full.txt`](https://developer.repebble.com/docs/c/llms-full.txt) | The C API reference | | [`/docs/pebblekit-js/llms-full.txt`](https://developer.repebble.com/docs/pebblekit-js/llms-full.txt) | The PebbleKit JS reference | [`/api-index.json`](https://developer.repebble.com/api-index.json) lists every C and PebbleKit JS symbol with its kind, signature, brief description, the platforms it is available on and the page that documents it. Agents can look up a function there instead of searching the reference pages. Add a line like the following to a prompt or an instruction file. For example: ``` Pebble SDK docs: https://developer.repebble.com/llms.txt. Fetch the .md version of any page before using an API. ``` ## Agent Instruction Files Most agents read an instruction file from the project directory before they start. `pebble new-project` writes these files when given the `--ai` flag: ``` $ pebble new-project --ai myproject ``` The command writes `CLAUDE.md` for Claude Code and `.cursor/rules/pebble.mdc` for Cursor from the same template. The files describe the project layout, the target platforms, the build, install and screenshot commands, how to use the emulator without a display, and where the documentation is. The flag also works with `--alloy` for an Alloy project. To add the files to an existing project, run the command in another directory and copy the files into the project. Other agents read other file names. The content is the same, so copy `CLAUDE.md` to the name the agent expects and edit the parts that describe the project. ## The Pebble Agent Skill [pebble-watchface-agent-skill](https://github.com/coredevices/pebble-watchface-agent-skill) is a skill for Claude Code that builds complete watchfaces and watchapps from a description. It chooses between a watchface and a watchapp and between C and Alloy, writes the project files, runs `pebble build`, installs the result in the emulator, takes a screenshot, checks the screenshot against the request and repeats until it matches. It then generates the app icons and preview images, and can publish the app with `pebble publish`. The skill targets emery (Pebble Time 2) by default and supports the other platforms as a second pass. Alloy projects are limited to emery and gabbro. To install it, clone the repository and follow its README, which lists the agents it supports. ## Working with the Emulator An agent can check its own work when it can install the app and see the screen. These commands do that without a physical watch: ``` $ pebble build$ pebble install --emulator emery$ pebble screenshot --no-open --emulator emery screenshot.png$ pebble emu-button click select --emulator emery$ pebble logs --emulator emery ``` `pebble screenshot --no-open` saves the image without opening it in a viewer, so the agent can read the file. `pebble emu-button` presses `back`, `up`, `select` or `down`; use `--duration` for a long press and `--repeat` to scroll. `pebble logs` shows `APP_LOG()` output from the running app. In an environment without a display, such as a container or CI, add `--vnc` to every command that uses `--emulator`. If the emulator gets into a strange state, such as a screenshot that shows a different app or an install that times out, `pebble kill` stops it and `pebble wipe` resets its storage; then install again. > Note: `pebble screenshot` does not create directories. Create the output directory before taking a screenshot into it. ## Target Platforms and the SDK Version State the SDK version and the target platforms in the instruction file or the prompt, together with the following: - The target platforms, as listed in `targetPlatforms` in `package.json`. The current platforms are aplite, basalt, chalk, diorite, emery, flint and gabbro. See [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) for screen sizes and colors, and [_Building for Every Pebble_](https://developer.repebble.com/guides/best-practices/building-for-every-pebble.md) for the compile-time defines. - The SDK version. The current version is 4.33.1; `pebble sdk list` lists the available SDK versions. - Alloy runs on emery and gabbro only. A project that targets older watches has to be written in C. - The timeline web API is no longer available. The Pebble mobile app does not sync pins from a server. Use [_local pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) instead. --- # Preparing a Submission Source: https://developer.repebble.com/guides/appstore-publishing/preparing-a-submission/ An appstore listing is built from the `.pbw` file, some text fields and a set of images. This guide lists what is required so that everything is ready before the submission starts. All three publishing routes described in [_Publishing an App_](https://developer.repebble.com/guides/appstore-publishing/publishing-an-app.md) use the same listing fields and the same image sizes. ## The `.pbw` File The appstore reads the app's type (watchface or watchapp), UUID, version and target platforms from the `.pbw` file. These values come from `package.json`, so check them before building the release: - `watchapp.watchface` decides whether the listing is a watchface or a watchapp. This cannot be changed in the Developer Dashboard. - `uuid` must be unique across the whole appstore. If another app already uses it, the submission is rejected. Each app keeps its UUID for all later releases, since the UUID is how a new release is matched to the existing listing. - `version` must contain only numbers and dots, and each segment must be 255 or less. Each release of an app must have a version that has not been used before. - `targetPlatforms` decides which platforms the `.pbw` contains binaries for. The appstore reads the platform list from `targetPlatforms` when it is present and otherwise from the binaries in the `.pbw`, and offers each binary to the watches that can run it: an aplite binary also serves basalt, diorite, flint and emery, a basalt binary also serves emery, a diorite binary also serves flint and emery, and a chalk binary also serves gabbro. Build for the newer platforms directly to use their full display. Read [_App Metadata_](https://developer.repebble.com/guides/tools-and-resources/app-metadata.md) for details. The `.pbw` file can be at most 4.4 MB. ## Listing Fields | Field | Watchface | Watchapp | Notes | | --- | --- | --- | --- | | App name | Required | Required | Defaults to the name in the `.pbw`. | | Description | Required | Required | Plain text. | | Category | - | Required | One of Daily, Tools & Utilities, Notifications, Remotes, Health & Fitness, Games. Watchfaces are listed under Faces automatically. | | Website URL | Optional | Optional | | | Source code URL | Optional | Optional | | | Release notes | Optional | Optional | Per release. | | Companion app | - | Optional | Name, Play Store URL and a 144×144 icon for an Android companion app. | > Note: The appstore does not support the timeline web API. Apps that use [_local pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) work as normal. ## Images All images are PNG, JPEG or GIF, at most 4.4 MB each. GIFs keep their animation. Screenshots must have exactly the dimensions of the platform's display and must not be framed in a watch outline. | Image | Size | Notes | | --- | --- | --- | | Screenshots for aplite, basalt, diorite, flint | 144×168 | Up to 5 per platform. | | Screenshots for chalk | 180×180 | Up to 5. | | Screenshots for emery | 200×228 | Up to 5. | | Screenshots for gabbro | 260×260 | Up to 5. | | Banner | 720×320 | Optional. One per platform. Shown at the top of the listing. | | Small icon | 80×80 | Watchapps only. | | Large icon | 144×144 | Watchapps only. | Icons are optional. When one or both icons are missing, the appstore generates them from the app name. Screenshots can be taken from the emulator with the `pebble` tool. The `--all-platforms` and `--gif-all-platforms` flags of `pebble screenshot` save one image per target platform under `screenshots/` in the project directory, already at the correct size: ``` $ pebble screenshot --all-platforms ``` Read [_pebble screenshot_](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md#screenshot) for the other options. ## Visibility Every app has one of three visibility settings. It is chosen when the app is submitted and can be changed at any time from the Developer Dashboard. | Setting | Search and browsing | Direct link and install | | --- | --- | --- | | Listed | Yes | Yes | | Unlisted | No | Yes | | Hidden | No | No | Apps submitted from the Developer Dashboard, the `pebble` tool or CloudPebble start as Listed. Use Unlisted for a beta that is shared by link only, and Hidden to take an app offline. Hiding an app does not remove it from watches where it is already installed, and cached appstore pages can take up to ten minutes to disappear. Visibility applies to the app as a whole. Each release also has its own published or draft state, and only published releases can be installed. --- # Publishing an App Source: https://developer.repebble.com/guides/appstore-publishing/publishing-an-app/ There are three ways to put an app in the Pebble appstore. The [Developer Dashboard](https://developer.repebble.com/dashboard) is a web form and is the only place where a listing can be edited after it is created. The `pebble` tool and CloudPebble build the app, capture screenshots and upload the result in one step. All three create the same listing, and an app submitted from one can be updated from another. Before starting, read [_Preparing a Submission_](https://developer.repebble.com/guides/appstore-publishing/preparing-a-submission.md) for the fields and image sizes that are required. Apps go live as soon as they are submitted. There is no review queue. Published listings can be browsed at [apps.repebble.com](https://apps.repebble.com) to see how other developers present their apps. ## Signing In The Developer Dashboard, the `pebble` tool and CloudPebble all use the same Pebble account. Sign in with Google, GitHub or Apple. A developer account is created on first sign-in. ![](https://developer.repebble.com/assets/images/guides/appstore-publishing/dashboard-signin.png) Use the same sign-in method each time. If the same email address was already used with a different method, the dashboard asks for a sign-in with the original method and then links the new one to the account. ## Using the Developer Dashboard ### Submitting a New App - Open the [Developer Dashboard](https://developer.repebble.com/dashboard) and click _New_. - Upload the `.pbw` file. The dashboard reads the app type, UUID, version and target platforms from it. Check the _Version Number_ and add _Release Notes_ if wanted. - Enter the _App Name_ and _Description_. For a watchapp, choose a _Category_. - For a watchapp, upload the _Small Icon_ and _Large Icon_, or leave them empty to have them generated. - Upload up to five screenshots for each target platform, and optionally a _Banner Image_ per platform. Screenshots are optional in the dashboard, but listings without them show nothing in the appstore apart from the icon and the text. - Add the _Website URL_, _Source Code URL_ and an Android companion app if there is one. - Choose the _App visibility_ and click _Submit App_. The app appears in the dashboard's app list with a public link of the form `https://apps.repebble.com/`. Share this link, or search for the app in the Pebble mobile app. ### Adding a New Release - Open the app in the dashboard and click _New Release_. - Upload the new `.pbw`. Its UUID must match the app, and its version must be one that has not been used before. - Add _Release Notes_. Leave _Publish immediately_ checked unless the release should stay as a draft. - Click _Create Release_. Each release in the app's release list has a _Publish_ or _Mark Draft_ toggle, so a release can be withdrawn without hiding the whole app. The appstore offers only the newest published release, and uses its binaries to decide which watches the app is compatible with. Older releases are not served, so dropping a platform from a new release makes the app unavailable on that hardware. ### Editing a Listing Click _Edit_ on the app's page to change the name, description, URLs, companion app, screenshots, banner and icons, and to change the _App visibility_ between Listed, Unlisted and Hidden. Changes are live after saving, apart from appstore pages that are cached for up to ten minutes. ## Using the `pebble` Tool The `publish` command builds the project, captures screenshots from the emulator and uploads everything. Run it from the project directory: ``` $ pebble login$ pebble publish ``` If the project's UUID is not in the appstore yet, the tool asks for the app name, version, description, source URL and, for a watchapp, the category and icons. If the UUID already belongs to one of the developer's apps, the tool uploads a new release of that app and only asks for screenshots. Screenshots are captured for every target platform with a rollover GIF by default. Pass `--no-gif-all-platforms` to skip the GIF and `--all-platforms` to capture static images instead, or choose _Select local screenshot/GIF files_ at the prompt to upload existing files. Local filenames must start with the platform name followed by an underscore, for example `emery_main.png` or `gabbro_menu.gif`. The files written by `pebble screenshot --all-platforms` already follow this pattern. Use `--release-notes` to set the release notes without being prompted: ``` $ pebble publish --release-notes "Fix the date on the first of the month" ``` ### Publishing from CI With `--non-interactive` the tool does not prompt. Everything a new app needs is passed as flags, and the sign-in token comes from the `PEBBLE_FIREBASE_ID_TOKEN` environment variable or the `--firebase-id-token` flag instead of a stored `pebble login` session: ``` $ export PEBBLE_FIREBASE_ID_TOKEN=...$ pebble publish --non-interactive \ --name "My Watchface" --description "A clean analog face." \ --source https://github.com/example/my-watchface \ --screenshots screenshots/emery_1.png screenshots/gabbro_1.png ``` For a watchapp add `--category` and, if generated icons are not wanted, `--icon-small` and `--icon-large`. For an existing app only `--release-notes`, `--version` and `--screenshots` are used. Without `--screenshots` the tool captures from the emulator, which needs a working emulator on the CI machine. A new app must have at least one screenshot. Releases made with `pebble publish` are published immediately and new apps are Listed. Change the visibility afterwards in the Developer Dashboard if needed. ## Using CloudPebble CloudPebble has a _Publish_ entry in the project sidebar for C and JavaScript SDK projects. It uses the account that is signed in to CloudPebble, so no extra sign-in is needed. - Build the project. Publishing uses the most recent successful build. - Open _Publish_. For a new app, fill in _App name_, _Version_, _Description_ and optionally _Source URL_. For a watchapp choose a _Category_, and either upload icons or leave _Auto-generate icons_ selected. - Under _Screenshots_, click _Auto-generate_ next to each platform to capture a screenshot and a short GIF from the emulator, click _Upload_ to use your own files, or _Auto-generate All_ to do every platform at once. - Click _Publish to App Store_. When the project's UUID is already in the appstore, the pane shows _Publish Update_ instead. Only the version, release notes and new screenshots are needed. If the version has already been used, CloudPebble reports _Version x.y already exists_ with a link to the project settings to change it. The success page links to the public appstore listing and to the Developer Dashboard for further edits. --- # Importing Apps from Rebble Source: https://developer.repebble.com/guides/appstore-publishing/importing-from-rebble/ Apps that were published through the Rebble Dev Portal can be imported into the Pebble appstore from the [Developer Dashboard](https://developer.repebble.com/dashboard). Importing copies the listing, releases and screenshots and keeps the app's original ID, so existing links keep working. After an import the app is managed from the Developer Dashboard like any other. ## Importing - Open the Developer Dashboard and click _Import_. - Choose _Import by Developer ID_ and enter your Pebble developer ID. The ID is in the URL of your developer page on the Pebble appstore and on the Rebble Dev Portal profile page. Click _Load All My Apps_. - To import single apps instead, choose _Import by App IDs_ and paste one app ID per line. The app ID is the last part of an appstore URL such as `apps.repebble.com/app/583f347dca3094d3b00006d3`. - Click _Import_ next to an app, or _Import All_. Apps are imported as Listed. Importing an app a second time refreshes its listing from Rebble and keeps the visibility that was set in the Developer Dashboard. ## Verifying Ownership The import checks the developer ID that Rebble has for each app against the ID of the signed-in account. Since the Pebble developer account is new, they never match on a first import, and the dashboard opens the _Verify App Ownership_ page. Until verification is approved the imported apps keep their original developer ID. They are listed on the verification page, not in the account's app list, and cannot be edited. - Check the list of apps submitted for verification. - Tick _I confirm that I am the original developer of these apps_ and click _Submit for Verification_. - An email arrives asking for proof. Reply with a screenshot of the apps and their IDs in the Rebble Dev Portal. Links to the source repositories or the original appstore submission emails help if the screenshot is not conclusive. Verification is done by a person and takes two to three business days. Once it is approved, the apps move to your developer account, and the account's developer ID becomes the original one so that the developer name shown in the appstore stays the same. ## Keeping Apps in Sync Apps that are still updated through the Rebble Dev Portal can be kept in sync instead of being moved. Open _Sync_ in the dashboard and turn it on per app. Synced apps are re-imported from Rebble once a day. Rebble is the source of truth for them, and the sync is skipped when the Pebble appstore already has a newer release than Rebble. --- # Appstore Publishing Source: https://developer.repebble.com/guides/appstore-publishing/ When an app is feature-complete and stable, the compiled `.pbw` file can be uploaded to the Pebble appstore so that users can find and install it from the Pebble mobile app. Listings are managed in the [Developer Dashboard](https://developer.repebble.com/dashboard). The `pebble` tool and CloudPebble can also upload apps and new releases directly. Published apps and watchfaces can be browsed at [apps.repebble.com](https://apps.repebble.com). Look at a few listings there to see how the icon, screenshots and description appear before preparing a submission. ![](https://developer.repebble.com/assets/images/guides/appstore-publishing/apps-repebble-com.png) To be listed in the Pebble appstore an app must: - Be built with a released SDK. - Use a UUID that is not already in use by another app in the appstore. - Have a title and a description, and a category if it is a watchapp. - Comply with the [legal agreements](https://developer.repebble.com/legal.md). Apps that were published in the Rebble appstore can be imported into the Developer Dashboard so that the same developer keeps managing them. ## Contents - [**Preparing a Submission**](https://developer.repebble.com/guides/appstore-publishing/preparing-a-submission.md) - What a Pebble appstore listing is made of, and the sizes and formats required for screenshots, icons and banners. - [**Publishing an App**](https://developer.repebble.com/guides/appstore-publishing/publishing-an-app.md) - How to submit an app and later releases from the Developer Dashboard, the `pebble` tool or CloudPebble. - [**Importing Apps from Rebble**](https://developer.repebble.com/guides/appstore-publishing/importing-from-rebble.md) - How to bring apps published in the Rebble appstore into the Developer Dashboard and prove ownership of them. --- # Best Practices Source: https://developer.repebble.com/guides/best-practices/ In order to get the most out of the Pebble SDK, there are numerous opportunities for optimization that can allow apps to use power more efficiently, display correctly on all display shapes and sizes, and help keep large projects maintainable. Information on these topics is contained in this collection of guides. Pebble recommends that developers try and incorporate as many of these practices into their apps as possible, to give themselves and users the best experience of their app. ## Contents - [**Building for Every Pebble**](https://developer.repebble.com/guides/best-practices/building-for-every-pebble.md) - How to write one app compatible with all Pebble smartwatches. - [**Conserving Battery Life**](https://developer.repebble.com/guides/best-practices/conserving-battery-life.md) - How to write an app to consume power as efficiently as possible. - [**Modular App Architecture**](https://developer.repebble.com/guides/best-practices/modular-app-architecture.md) - How to break up a complex app into smaller pieces for managablilty, modularity and reusability. --- # Building for Every Pebble Source: https://developer.repebble.com/guides/best-practices/building-for-every-pebble/ The difference in capabilities between the various Pebble hardware platforms are listed in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md). For example, the Basalt, Chalk and Emery platforms support 64 colors, whereas the Aplite, Diorite and Flint platforms only support two colors. This can make developing apps with rich color layouts difficult when considering compatibility with other non-color hardware. Another example is using platform specific APIs such as Health or Dictation. To make life simple for users, developers should strive to write one app that can be used on all platforms. To help make this task simpler for developers, the Pebble SDK provides numerous methods to accommodate different hardware capabilities in code. ## Preprocessor Directives It is possible to specify certain blocks of code to be compiled for specific purposes by using the `#ifdef` preprocessor statement. For example, the [`Dictation`](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") API should be excluded on platforms with no microphone: ``` #if defined(PBL\_MICROPHONE)// Start dictation UIdictation\_session\_start(s\_dictation\_session);#else// Microphone is not availabletext\_layer\_set\_text(s\_some\_layer, "Dictation not available!");#endif ``` When designing UI layouts, any use of colors on compatible platforms can be adapted to either black or white on non-color platforms. The `PBL_COLOR` and `PBL_BW` symbols will be defined at compile time when appropriate capabilities are available: ``` #if defined(PBL\_COLOR)text\_layer\_set\_text\_color(s\_text\_layer, GColorRed);text\_layer\_set\_background\_color(s\_text\_layer, GColorChromeYellow);#elsetext\_layer\_set\_text\_color(s\_text\_layer, GColorWhite);text\_layer\_set\_background\_color(s\_text\_layer, GColorBlack);#endif ``` This is useful for blocks of multiple statements that change depending on the availability of color support. For single statements, this can also be achieved by using the [`PBL_IF_COLOR_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE "PBL\_IF\_COLOR\_ELSE") macro. ``` window\_set\_background\_color(s\_main\_window, PBL\_IF\_COLOR\_ELSE(GColorJaegerGreen, GColorBlack)); ``` See below for a complete list of defines and macros available. ## Available Defines and Macros The tables below show a complete summary of all the defines and associated macros available to conditionally compile or omit feature-dependant code. The macros are well-suited for individual value selection, whereas the defines are better used to select an entire block of code. | Define | MACRO | Available | | --- | --- | --- | | `PBL_BW` | `PBL_IF_BW_ELSE()` | Running on hardware that supports only black and white. | | `PBL_COLOR` | `PBL_IF_COLOR_ELSE()` | Running on hardware that supports 64 colors. | | `PBL_COMPASS` | None | Running on hardware that includes a compass. | | `PBL_MICROPHONE` | `PBL_IF_MICROPHONE_ELSE()` | Running on hardware that includes a microphone. | | `PBL_HEALTH` | `PBL_IF_HEALTH_ELSE()` | Running on hardware that supports Pebble Health and the `HealthService` API. | | `PBL_RGB_BACKLIGHT` | None | Running on hardware that has an RGB backlight | | `PBL_RECT` | `PBL_IF_RECT_ELSE()` | Running on hardware with a rectangular display. | | `PBL_ROUND` | `PBL_IF_ROUND_ELSE()` | Running on hardware with a round display. | | `PBL_SMARTSTRAP` | `PBL_IF_SMARTSTRAP_ELSE()` | Running on hardware with smartstrap support. | | `PBL_SMARTSTRAP_POWER` | None | Running on hardware that includes a smartstrap connector capable of supplying power. | | `PBL_SPEAKER` | None | Running on hardware that includes a speaker. | | `PBL_TOUCH` | None | Running on hardware that includes a touch screen. | | `PBL_RGB_BACKLIGHT` | None | Running on hardware with an RGB backlight. | | `PBL_DISPLAY_WIDTH` | None | Determine the screen width in pixels. | | `PBL_DISPLAY_HEIGHT` | None | Determine the screen height in pixels. | | `PBL_PLATFORM_APLITE` | None | Built for Pebble/Pebble Steel. | | `PBL_PLATFORM_BASALT` | None | Built for Pebble Time/Pebble Time Steel. | | `PBL_PLATFORM_CHALK` | None | Built for Pebble Time Round. | | `PBL_PLATFORM_DIORITE` | None | Built for Pebble 2. | | `PBL_PLATFORM_EMERY` | None | Built for Pebble Time 2. | | `PBL_PLATFORM_FLINT` | None | Built for Pebble 2 Duo. | | `PBL_PLATFORM_GABBRO` | None | Built for Pebble Round 2. | | `PBL_SDK_2` | None | Compiling with SDK 2.x (deprecated). | | `PBL_SDK_3` | None | Compiling with SDK 3.x. or 4.x. | > Note: It is strongly recommended to conditionally compile code using applicable feature defines instead of `PBL_PLATFORM` defines to be as specific as possible. ## Selecting Values by Platform [`PBL_PLATFORM_SWITCH()`](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_SWITCH "PBL\_PLATFORM\_SWITCH") picks one of seven values by platform. The [`PlatformType`](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformType "PlatformType") enum names every platform and [`PBL_PLATFORM_TYPE_CURRENT`](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_TYPE_CURRENT "PBL\_PLATFORM\_TYPE\_CURRENT") is the platform of the current build: ``` // Width of the action bar on each platformint width = PBL\_PLATFORM\_SWITCH(PBL\_PLATFORM\_TYPE\_CURRENT,/\*aplite\*/ 30, /\*basalt\*/ 30, /\*chalk\*/ 40, /\*diorite\*/ 30,/\*emery\*/ 34, /\*flint\*/ 30, /\*gabbro\*/ 40); ``` The SDK defines [`ACTION_BAR_WIDTH`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ACTION_BAR_WIDTH "ACTION\_BAR\_WIDTH") in exactly this way. The macro expands to a chain of comparisons, so with [`PBL_PLATFORM_TYPE_CURRENT`](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_TYPE_CURRENT "PBL\_PLATFORM\_TYPE\_CURRENT") as the first argument the compiler resolves it at compile time and the result is the same as the `PBL_IF_*` macros above. It only becomes a runtime choice when the first argument is a variable, for example a [`PlatformType`](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformType "PlatformType") the app has derived from a [`WatchInfoModel`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoModel "WatchInfoModel") to lay out content for a different watch. The first argument is evaluated once per comparison, so pass a variable rather than a function call. [`PBL_PLATFORM_SWITCH_DEFAULT()`](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_SWITCH_DEFAULT "PBL\_PLATFORM\_SWITCH\_DEFAULT") takes an extra default value that is returned for a platform the app does not know about. ## API Detection In addition to platform and capabilities detection, we now provide API detection to detect if a specific API method is available. This approach could be considered future-proof, since platforms and capabilities may come and go. Let's take a look at a simple example: ``` #if PBL\_API\_EXISTS(health\_service\_peek\_current\_value) // Do something if specific Health API exists#endif ``` ## Avoid Hardcoded Layout Values With the multiple display shapes and resolutions available, developers should try and avoid hardcoding layout values. Consider the example below: ``` static void window\_load(Window \*window) {// Create a full-screen Layer - BADs\_some\_layer = layer\_create(GRect(0, 0, 144, 168));} ``` The hardcoded width and height of this layer will cover the entire screen on Aplite, Basalt, Diorite and Flint, but not on Chalk or Emery. This kind of screen size-dependant calculation should use the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") bounds of the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") itself: ``` static void window\_load(Window \*window) {// Get the unobstructed bounds of the WindowLayer window\_layer = window\_get\_root\_layer(window);GRect window\_bounds = layer\_get\_unobstructed\_bounds(window\_layer);// Properly create a full-screen Layer - GOODs\_some\_layer = layer\_create(window\_bounds);} ``` Another common use of this sort of construction is to make a [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") that is half the unobstructed screen height. This can also be correctly achieved using the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") unobstructed bounds: ``` GRect layer\_bounds = window\_bounds;layer\_bounds.size.h /= 2;// Create a Layer that is half the screen heights\_some\_layer = layer\_create(layer\_bounds); ``` This approach is also advantageous in simplifying updating an app for a future new screen size, as proportional layout values will adapt as appropriate when the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") unobstructed bounds change. ## Screen Sizes To ease the introduction of the Emery platform, the Pebble SDK introduced new compiler directives to allow developers to determine the screen width and height. This is preferable to using platform detection, since multiple platforms share the same screen width and height. ``` #if PBL\_DISPLAY\_HEIGHT == 228uint8\_t offset\_y = 100;#elif PBL\_DISPLAY\_HEIGHT == 180uint8\_t offset\_y = 80;#elseuint8\_t offset\_y = 60;#endif ``` | Platform | Display | Shape | | --- | --- | --- | | aplite, basalt, diorite, flint | 144 × 168 | Rectangular | | chalk | 180 × 180 | Round | | emery | 200 × 228 | Rectangular | | gabbro | 260 × 260 | Round | > Note: Although this method is preferable to platform detection, it is better to dynamically calculate the display width and height based on the unobstructed bounds of the root layer. ## Pebble C WatchInfo The [`WatchInfo`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md "WatchInfo") API can be used to determine exactly which Pebble model and color an app is running on. Apps can use this information to dynamically modify their layout or behavior depending on which Pebble the user is wearing. For example, the display on Pebble Steel is located at a different vertical position relative to the buttons than on Pebble Time. Any on-screen button hints can be adjusted to compensate for this using [`WatchInfoModel`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoModel "WatchInfoModel"). ``` static void window\_load(Window \*window) {Layer window\_layer = window\_get\_root\_layer(window);GRect window\_bounds = layer\_get\_bounds(window\_layer);int button\_height, y\_offset;// Conditionally set layout parametersswitch(watch\_info\_get\_model()) {case WATCH\_INFO\_MODEL\_PEBBLE\_STEEL:y\_offset = 64;button\_height = 44;break;case WATCH\_INFO\_MODEL\_PEBBLE\_TIME:y\_offset = 58;button\_height = 56;break;/\* Other cases \*/default:y\_offset = 0;button\_height = 0;break;}// Set the Layer frameGRect layer\_frame = GRect(0, y\_offset, window\_bounds.size.w, button\_height);// Create the Layers\_label\_layer = text\_layer\_create(layer\_frame);layer\_add\_child(window\_layer, text\_layer\_get\_layer(s\_label\_layer));/\* Other UI code \*/} ``` Developers can also use [`WatchInfoColor`](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoColor "WatchInfoColor") values to theme an app for each available color of Pebble. ``` static void window\_load(Window \*window) {GColor text\_color, background\_color;// Choose different theme colors per watch colorswitch(watch\_info\_get\_color()) {case WATCH\_INFO\_COLOR\_RED:// Red themetext\_color = GColorWhite;background\_color = GColorRed;break;case WATCH\_INFO\_COLOR\_BLUE:// Blue themetext\_color = GColorBlack;background\_color = GColorVeryLightBlue;break;/\* Other cases \*/default:text\_color = GColorBlack;background\_color = GColorWhite;break;}// Use the conditionally set valuetext\_layer\_set\_text\_color(s\_label\_layer, text\_color);text\_layer\_set\_background\_color(s\_label\_layer, background\_color);/\* Other UI code \*/} ``` ## PebbleKit JS Watch Info Similar to [_Pebble C WatchInfo_](#pebble-c-watchinfo) above, the PebbleKit JS `Pebble.getActiveWatchInfo()` method allows developers to determine which model and color of Pebble the user is wearing, as well as the firmware version running on it. For example, to obtain the model of the watch: > Note: See the section below to avoid problem using this function on older app version. ``` // Get the watch infovar info = Pebble.getActiveWatchInfo();console.log('Pebble model: ' + info.model); ``` ## Detecting Platform-specific JS Features A number of features in PebbleKit JS (such as `Pebble.getActiveWatchInfo()`) exist on SDK 3.x. If an app tries to use any of these on an older Pebble mobile app version where they are not available, the JS app will crash. To prevent this, be sure to check for the availability of the function before calling it. For example, in the case of `Pebble.getActiveWatchInfo()`: ``` if (Pebble.getActiveWatchInfo) {// Available.var info = Pebble.getActiveWatchInfo();console.log('Pebble model: ' + info.model);} else {// Gracefully handle no info available} ``` ## Platform-specific Resources With the availability of color support on Basalt, Chalk and Emery, developers may wish to include color versions of resources that had previously been pre-processed for Pebble's black and white display. Including both versions of the resource is expensive from a resource storage perspective, and lays the burden of packing redundant color resources in an Aplite, Diorite or Flint app when built for multiple platforms. To solve this problem, the Pebble SDK allows developers to specify which version of an image resource is to be used for each display type, using `~bw` or `~color` appended to a file name. Resources can also be bundled only with specific platforms using the `targetPlatforms` property for each resource. For more details about packaging resources specific to each platform, as well as more tags available similar to `~color`, read [_Platform-specific Resources_](https://developer.repebble.com/guides/app-resources/platform-specific.md). ## Multiple Display Shapes With the introduction of the Chalk platform, a new round display type is available with increased pixel resolution. To distinguish between the two possible shapes of display, developers can use defines to conditionally include code segments: ``` #if defined(PBL\_RECT)printf("This is a rectangular display!");#elif defined(PBL\_ROUND)printf("This is a round display!");#endif ``` Another approach to this conditional compilation is to use the [`PBL_IF_RECT_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_RECT_ELSE "PBL\_IF\_RECT\_ELSE") and [`PBL_IF_ROUND_ELSE()`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_ROUND_ELSE "PBL\_IF\_ROUND\_ELSE") macros, allowing values to be inserted into expressions that might otherwise require a set of `#define` statements similar to the previous example. This would result in needless verbosity of four extra lines of code when only one is actually needed. These are used in the following manner: ``` // Conditionally print out the shape of the displayprintf("This is a %s display!", PBL\_IF\_RECT\_ELSE("rectangular", "round")); ``` This mechanism is best used with window bounds-derived layout size and position value. See the [_Avoid Hardcoded Layout Values_](#avoid-hardcoded-layout-values) section above for more information. Making good use of the builtin [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") types will also help safeguard apps against display shape and size changes. Another thing to consider is rendering text on a round display. Due to the rounded corners, each horizontal line of text will have a different available width, depending on its vertical position. --- # Conserving Battery Life Source: https://developer.repebble.com/guides/best-practices/conserving-battery-life/ One of Pebble's strengths is its long battery life. This is due in part to using a low-power display technology, conservative use of the backlight, and allowing the processor to sleep whenever possible. It therefore follows that apps which misuse high-power APIs or prevent power-saving mechanisms from working will detract from the user's battery life. Several common causes of battery drain in apps are discussed in this guide, alongside suggestions to help avoid them. ## Time Awake Because the watch tries to sleep as much as possible to conserve power, any app that keeps the watch awake will incur significant a battery penalty. Examples of such apps include those that frequently use animations, sensors, Bluetooth communications, and vibrations. ### Animations and Display Updates A common cause of such a drain are long-running animations that cause frequent display updates. For example, a watchface that plays a half-second [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") for every second that ticks by will drain the battery faster than one that does so only once per minute. The latter approach will allow a lot more time for the watch to sleep. ``` static void tick\_handler(struct tm \*tick\_time, TimeUnits changed) {// Update timeset\_time\_digits(tick\_time);// Only update once a minuteif(tick\_time-\>tm\_sec == 0) {play\_animation();}} ``` This also applies to apps that make use of short-interval [`Timer`](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer")s, which is another method of creating animations. Consider giving users the option to reduce or disable animations to further conserve power, as well as removing or shortening animations that are not essential to the app's function or aesthetic. However, not all animations are bad. Efficient use of the battery can be maintained if the animations are played at more intelligent times. For example, when the user is holding their arm to view the screen (see [`pebble_glancing_demo`](https://github.com/pebble-hacks/pebble_glancing_demo)) or only when a tap or wrist shake is detected: ``` static void accel\_tap\_handler(AccelAxisType axis, int32\_t direction) {// Animate when the user flicks their wristplay\_animation();} ``` ``` accel\_tap\_service\_subscribe(tap\_handler); ``` ### Tick Updates Many watchfaces unecessarily tick once a second by using the [`SECOND_UNIT`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#SECOND_UNIT "SECOND\_UNIT") constant value with the [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService"), when they only update the display once a minute. By using the [`MINUTE_UNIT`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#MINUTE_UNIT "MINUTE\_UNIT") instead, the amount of times the watch is woken up per minute is reduced. ``` // Only tick once a minute, much more time asleeptick\_timer\_service\_subscribe(MINUTE\_UNIT, tick\_handler); ``` If possible, give users the choice to disable the second hand tick and/or animation to further save power. Extremely minimal watchfaces may also use the [`HOUR_UNIT`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#HOUR_UNIT "HOUR\_UNIT") value to only be updated once per hour. This factor is especially important for Pebble Time Round users. On this platform the reduced battery capacity means that a watchface with animations that play every second could reduce this to one day or less. Consider offering configuration options to reducing tick updates on this platform to save power where it at a premium. ### Sensor Usage Apps that make frequent usage of Pebble's onboard accelerometer and compass sensors will also prevent the watch from going to sleep and consume more battery power. The [`AccelerometerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md "AccelerometerService") API features the ability to configure the sampling rate and number of samples received per update, allowing batching of data into less frequent updates. By receiving updates less frequently, the battery will last longer. ``` // Batch samples into sets of 10 per callbackconst uint32\_t num\_samples = 10;// Sample at 10 Hzaccel\_service\_set\_sampling\_rate(ACCEL\_SAMPLING\_10HZ);// With this combination, only wake up the app once per second!accel\_data\_service\_subscribe(num\_samples, accel\_data\_handler); ``` Similarly, the [`CompassService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md "CompassService") API allows a filter to be set on the heading updates, allowing an app to only be notified per every 45 degree angle change, for example. ``` // Only update if the heading changes significantlycompass\_service\_set\_heading\_filter(TRIG\_MAX\_ANGLE / 36); ``` In addition, making frequent use of the [`Dictation`](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") API will also keep the watch awake, and also incur a penalty for keeping the Bluetooth connection alive. Consider using the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API to remember previous user input and instead present a list of previous inputs if appropriate to reduce usage of this API. ``` static void dictation\_session\_callback(DictationSession \*session, DictationSessionStatus status,char \*transcription, void \*context) {if(status == DictationSessionStatusSuccess) {// Display the dictated textsnprintf(s\_last\_text, sizeof(s\_last\_text), "Transcription:\n\n%s",transcription);text\_layer\_set\_text(s\_output\_layer, s\_last\_text);// Save for later!const int last\_text\_key = 0;persist\_write\_string(last\_text\_key, s\_last\_text);}} ``` ### Bluetooth Usage Hinted at above, frequent use of the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") API to send and recieve data will cause the Bluetooth connection to enter a more responsive state, which consumes much more power. A small time after a message is sent, the connection will return back to a low-power state. The 'sniff interval' determines how often the API checks for new messages from the phone, and should be let in the default [`SNIFF_INTERVAL_NORMAL`](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#SNIFF_INTERVAL_NORMAL "SNIFF\_INTERVAL\_NORMAL") state as much as possible. Consider how infrequent communication activities can be to save power and maintain functionality, and how data obtained over the Bluetooth connection can be cached using the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API to reduce the frequency of updates (for example, weather information in watchface). If the reduced sniff state must be used to transfer large amounts of data quickly, be sure to return to the low-power state as soon as the transfer is complete: ``` // Return to low power Bluetooth stateapp\_comm\_set\_sniff\_interval(SNIFF\_INTERVAL\_NORMAL); ``` ## Backlight Usage The backlight LED is another large consumer of battery power. System-level backlight settings may see the backlight turn on for a few seconds every time a button is pressed. While this setting is out of the hands of developers, apps can work to reduce the backlight on-time by minimizing the number of button presses required to operate them. For example, use an [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") to execute common actions with one button press instead of a long scrolling [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer"). While the [`Light`](https://developer.repebble.com/docs/c/User_Interface/Light.md "Light") API is available to manually turn the backlight on, it should not be used for more than very short periods, if at all. Apps that keep the backlight on all the time will not last more than a few hours. If the backlight must be kept on for an extended period, make sure to return to the automatic mode as soon as possible: ``` // Return to automatic backlight controllight\_enable(false); ``` ## Vibration Motor Usage As a physical converter of electrical to mechanical energy, the vibration motor also consumes a lot of power. Users can elect to use Quiet Time or turn off vibration for notifications to save power, but apps can also contribute to this effort. Try and keep the use of the [`Vibes`](https://developer.repebble.com/docs/c/User_Interface/Vibes.md "Vibes") API to a minimum and giving user the option to disable any vibrations the app emits. Another method to reduce vibrator power consumtion is to shorten the length of any custom sequences used. ## Learn More To learn more about power consumtion on Pebble and how battery life can be extended through app design choices, watch the presentation below given at the 2014 Developer Retreat. --- # Modular App Architecture Source: https://developer.repebble.com/guides/best-practices/modular-app-architecture/ Most Pebble projects (such as a simple watchface) work fine as a single-file project. This means that all the code is located in one `.c` file. However, as the size of a single-file Pebble project increases, it can become harder to keep track of where all the different components are located, and to track down how they interact with each other. For example, a hypothetical app may have many [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")s, perform communication over [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") with many types of data items, store and persist a large number of data items, or include components that may be valuable in other projects. As a first example, the Pebble SDK is already composed of separate modules such as [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"), [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") etc. The implementation of each is separate from the rest and the interface for developers to use in each module is clearly defined and will rarely change. This guide aims to provide techniques that can be used to break up such an app. The advantages of a modular approach include: - App [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")s can be kept separate and are easier to work on. - A clearly defined interface between components ensures internal changes do not affect other modules. - Modules can be re-used in other projects, or even made into sharable libraries. - Inter-component variable dependencies do not occur, which can otherwise cause problems if their type or size changes. - Sub-component complexity is hidden in each module. - Simpler individual files promote maintainability. - Modules can be more easily tested. ## A Basic Project A basic Pebble project starts life with the `new-project` command: ``` $ pebble new-project modular-project ``` This new project will contain the following default file structure. The `modular-project.c` file will contain the entire app, including `main()`, `init()` and `deinit()`, as well as a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") and a child [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). ``` modular-project/ resources/ src/ c/ modular-project.c package.json wscript ``` The `wscript` generated by `new-project` compiles every `.c` file under `src/c/`, including subdirectories, so new source files only need to be placed there to be built. Projects created with older SDKs that keep their sources in `src/` directly must either move them to `src/c/` or change the `ant_glob()` pattern in `wscript` to include `src/c/**/*.c` as well. For most projects, this structure is perfectly adequate. When the `.c` file grows to several hundred lines long and incorporates several sub-components with many points of interaction with each other through shared variables, the complexity reaches a point where some new techniques are needed. ## Creating a Module In this context, a 'module' can be thought of as a C header and source file 'pair', a `.h` file describing the module's interface and a `.c` file containing the actual logic and code. The header contains standard statements to prevent redefinition from being `#include`d multiple times, as well as all the function prototypes the module makes available for other modules to use. By making a sub-component of the app into a module, the need for messy global variables is removed and a clear interface between them is defined. The files themselves are located in a `modules` directory inside the project's `src/c` directory, keeping them in a separate location to other components of the app. Thus the structure of the project with a `data` module added (and explained below) is now this: ``` modular-project/ resources/ src/ c/ modules/ data.h data.c modular-project.c package.json wscript ``` The example module's pair of files is shown below. It manages a dynamically allocated array of integers, and includes an interface to setting and getting values from the array. The array itself is private to the module thanks for the [`static`](https://en.wikipedia.org/wiki/Static_(keyword)) keyword. This technique allows other components of the app to call the 'getters' and 'setters' with the correct parameters as per the module's interface, without worrying about the implementation details. `src/c/modules/data.h` ``` #pragma once // Prevent errors by being included multiple times#include \ // Pebble SDK symbolsvoid data\_init(int array\_length);void data\_deinit();void data\_set\_array\_value(int index, int new\_value);int data\_get\_array\_value(int index); ``` `src/c/modules/data.c` ``` #include "data.h"static int\* s\_array;void data\_init(int array\_length) {if(!s\_array) {s\_array = (int\*)malloc(array\_length \* sizeof(int));}}void data\_deinit() {if(s\_array) {free(s\_array);s\_array = NULL;}}void data\_set\_array\_value(int index, int new\_value) {s\_array[index] = new\_value;}int data\_get\_array\_value(int index) {return s\_array[index];} ``` ## Keep Multiple Windows Separate The [`Window Stack`](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md "Window Stack") lifecycle makes the task of keeping each [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") separate quite easy. Each one has a `.load` and `.unload` handler which should be used to create and destroy its UI components and other data. The first step to modularizing the new app is to keep each [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") in its own module. The first [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")'s code can be moved out of `src/c/modular-project.c` into a new module in `src/c/windows/` called 'main\_window': `src/c/windows/main_window.h` ``` #pragma once#include \void main\_window\_push(); ``` `src/c/windows/main_window.c` ``` #include "main\_window.h"static Window \*s\_window;static void window\_load(Window \*window) {Layer \*window\_layer = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(window\_layer);}static void window\_unload(Window \*window) {window\_destroy(s\_window);}void main\_window\_push() {if(!s\_window) {s\_window = window\_create();window\_set\_window\_handlers(s\_window, (WindowHandlers) {.load = window\_load,.unload = window\_unload,});}window\_stack\_push(s\_window, true);} ``` ## Keeping Main Clear After moving the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") code out of the main `.c` file, it can be safely renamed `main.c` to reflect its contents. This allows the main `.c` file to show a high-level overview of the app as a whole. Simply `#include` the required modules and windows to initialize and deinitialize the rest of the app as necessary: `src/c/main.c` ``` #include \#include "modules/data.h"#include "windows/main\_window.h"static void init() {const int array\_size = 16;data\_init(array\_size);main\_window\_push();}static void deinit() {data\_deinit();}int main() {init();app\_event\_loop();deinit();} ``` Thus the structure of the project is now: ``` modular-project/ resources/ src/ c/ modules/ data.h data.c windows/ main_window.h main_window.c main.c package.json wscript ``` With this structured approach to organizing the different functional components of an app, the maintainability of the project will not suffer as it grows in size and complexity. A useful module can even be shared and reused as a library, which is preferrable to pasting chunks of code that may have other messy dependencies elsewhere in the project. --- # Common Runtime Errors Source: https://developer.repebble.com/guides/debugging/common-runtime-errors/ Whether just beginning to create apps for the Pebble platform or are creating a more complex app, the output from app logs can be very useful in tracking down problems with an app's code. Some examples of common problems are explored here, including some examples to help gain familiarity with compiler output. In contrast with syntactical errors in written code (See [_Common Syntax Errors_](https://developer.repebble.com/guides/debugging/common-syntax-errors.md)), there can also be problems that only occur when the app is actually run on a Pebble. The reason for this is that perfectly valid C code can sometimes cause improper behavior that is incompatible with the hardware. These problems can manifest themselves as an app crashing and very little other information available as to what the cause was, which means that they can take an abnormally long time to diagnose and fix. One option to help track down the offending lines is to begin at the start of app initialization and use a call to [`APP_LOG()`](https://developer.repebble.com/docs/c/Foundation/Logging.md#app_log "app\_log") to establish where execution stops. If the message in the log call appears, then execution has at least reached that point. Move the call further through logical execution until it ceases to appear, as it will then be after the point where the app crashes. ## Null Pointers The Pebble SDK uses a dynamic memory allocation model, meaning that all the SDK objects and structures available for use by developers are allocated as and when needed. This model has the advantage that only the immediately needed data and objects can be kept in memory and unloaded when they are not needed, increasing the scale and capability of apps that can be created. In this paradigm a structure is first declared as a pointer (which may be given an initial value of `NULL`) before being fully allocated a structure later in the app's initialization. Therefore one of the most common problems that can arise is that of the developer attempting to use an unallocated structure or data item. For example, the following code segment will cause a crash: ``` Window \*main\_window;static void init() {// Attempting to push an uninitialized Window!window\_stack\_push(main\_window, true);} ``` The compiler will not report this, but when run the app will crash before the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") can be displayed, with an error message sent to the console output along the following lines: ``` [INFO] E ault_handling.c:77 App fault! {f23aecb8-bdb5-4d6b-b270-602a1940575e} PC: 0x8016716 LR: 0x8016713 [WARNING] Program Counter (PC): 0x8016716 ??? [WARNING] Link Register (LR): 0x8016713 ??? ``` When possible, the pebble tool will tell the developer the PC (Program Counter, or which statement is currently being executed) and LR (Link Register, address to return to when the current function scope ends) addresses and line numbers at the time of the crash, which may help indicate the source of the problem. This problem can be fixed by ensuring that any data structures declared as pointers are properly allocated using the appropriate `_create()` SDK functions before they are used as arguments: ``` Window \*main\_window;static void init(void) {main\_window = window\_create();window\_stack\_push(main\_window, true);} ``` In situations where available heap space is limited, `_create()` functions may return `NULL`, and the object will not be allocated. Apps can detect this situation as follows: ``` Window \*main\_window;static void init(void) {main\_window = window\_create();if(main\_window != NULL) {// Allocation was successful!window\_stack\_push(main\_window, true);} else {// The Window could not be allocated! // Tell the user that the operation could not be completedtext\_layer\_set\_text(s\_output\_layer, "Unable to use this feature at the moment.");}} ``` This `NULL` pointer error can also occur to any dynamically allocated structure or variable of the developer's own creation outside the SDK. For example, a typical dynamically allocated array will cause a crash if it is used before it is allocated: ``` char \*array;// Array is still NULL!array[0] = 'a'; ``` This problem can be fixed in a similar manner as before by making sure the array is properly allocated before it is used: ``` char \*array = (char\*)malloc(8 \* sizeof(char));array[0] = 'a'; ``` As mentioned above for [`window_create()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_create "window\_create"), be sure also check the [return value](http://pubs.opengroup.org/onlinepubs/009695399/functions/malloc.html) of `malloc()` to determine whether the memory allocation requested was completed successfully: ``` array = (char\*)malloc(8 \* sizeof(char));// Check the malloc() was successfulif(array != NULL) {array[0] = 'a';} else {// Gracefully handle the failed situation} ``` ## Outside Array Bounds Another problem that can look OK to the compiler, but cause an error at runtime is improper use of arrays, such as attempting to access an array index outside the array's bounds. This can occur when a loop is set up to iterate through an array, but the size of the array is reduced or the loop conditions change. For example, the array iteration below will not cause a crash, but includes the use of 'magic numbers' that can make a program brittle and prone to errors when these numbers change: ``` int \*array;static void init(void) {array = (int\*)malloc(8 \* sizeof(int));for(int i = 0; i \< 8; i++) {array[i] = i \* i;}} ``` If the size of the allocated array is reduced, the app will crash when the iterative loop goes outside the array bounds: ``` int \*array;static void init(void) {array = (int\*)malloc(4 \* sizeof(int));for(int i = 0; i \< 8; i++) {array[i] = i \* i;// Crash when i == 4!}} ``` Since the number of loop iterations is linked to the size of the array, this problem can be avoided by defining the size of the array in advance in one place, and then using that value everywhere the size of the array is needed: ``` #define ARRAY\_SIZE 4int \*array;static void init(void) {array = (int\*)malloc(ARRAY\_SIZE \* sizeof(int));for(int i = 0; i \< ARRAY\_SIZE; i++) {array[i] = i \* i;}} ``` An alternative solution to the above is to use either the `ARRAY_LENGTH()` macro or the `sizeof()` function to programmatically determine the size of the array to be looped over. --- # Common Syntax Errors Source: https://developer.repebble.com/guides/debugging/common-syntax-errors/ If a developer is relatively new to writing Pebble apps (or new to the C language in general), there may be times when problems with an app's code will cause compilation errors. Some types of errors with the code itself can be detected by the compiler and this helps reduce the number that cause problems when the code is run on Pebble. These are problems with how app code is written, as opposed to runtime errors (discussed in [_Common Runtime Errors_](https://developer.repebble.com/guides/debugging/common-runtime-errors.md)), which may include breaking the rules of the C language or bad practices that the compiler is able to detect and show as an error. The following are some examples. ### Undeclared Variables This error means that a variable that has been referenced is not available in the current scope. ``` ../src/main.c: In function 'toggle_logging': ../src/main.c:33:6: error: 'is_now_logging' undeclared (first use in this function) if(is_now_logging == true) { ^ ``` In the above example, the symbol `is_now_logging` has been used in the `toggle_logging` function, but it was not first declared there. This could be because the declaring line has been deleted, or it was expected to be available globally, but isn't. To fix this, consider where else the symbol is required. If it is needed in other functions, move the declaration to a global scope (outside any function). If it is needed only for this function, declare it before the offending line (here line `33`). ### Undeclared Functions Another variant of the above problem can occur when declaring new functions in a code file. Due to the nature of C compilation, any function a developer attempts to call must have been previously encountered by the compiler in order to be visible. This can be done through [forward declaration](http://en.wikipedia.org/wiki/Forward_declaration). For example, the code segment below will not compile: ``` static void window\_load(Window \*window) {my\_function();}void my\_function() {// Some code here} ``` The compiler will report this with an 'implicit declaration' error, as the app has implied the function's existence by calling it, even though the compiler has not seen it previously: ``` ../src/function-visibility.c: In function 'window_load': ../src/function-visibility.c:6:3: error: implicit declaration of function 'my_function' [-Werror=implicit-function-declaration] my_function(); ^ ``` This is because the _declaration_ of `my_function()` occurs after it is called in `window_load()`. There are two options to fix this. - Move the function declaration above any calls to it, so it has been encountered by the compiler: ``` void my\_function() {// Some code here}static void window\_load(Window \*window) {my\_function();} ``` - Declare the function by prototype before it is called, and provide the implementation later: ``` void my\_function();static void window\_load(Window \*window) {my\_function();}void my\_function() {// Some code here} ``` ### Too Few Arguments When creating functions with argument lists, sometimes the requirements of the function change and the developer forgets to update the places where it is called. ``` ../src/main.c: In function 'select_click_handler': ../src/main.c:57:3: error: too few arguments to function 'toggle_logging' toggle_logging(); ^ ../src/main.c:32:13: note: declared here static void toggle_logging(bool will_log) { ^ ``` The example above reports that the app tried to call the `toggle_logging()` function in `select_click_handler()` on line 57, but did not supply enough arguments. The argument list expected in the function definition is shown in the second part of the output message, which here exists on line 32 and expects an extra value of type `bool`. To fix this, establish which version of the function is required, and update either the calls or the declaration to match. ### Incorrect Callback Implementations In the Pebble SDK there are many instances where the developer must implement a function signature required for callbacks, such as for a [`WindowHandlers`](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers "WindowHandlers") object. This means that when implementing the handler the developer-defined callback must match the return type and argument list specified in the API documentation. For example, the [`WindowHandler`](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler "WindowHandler") callback (used for the `load` and `unload` events in a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")'s lifecycle) has the following signature: ``` typedef void(\* WindowHandler)(struct Window \*window) ``` This specifies a return type of `void` and a single argument: a pointer of type [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). Therefore the implemented callback should look like this: ``` void window\_load(Window \*window) {} ``` If the developer does not specify the correct return type and argument list in their callback implementation, the compiler will let them know with an error like the following, stating that the type of function passed by the developer does not match that which is expected: ``` ../src/main.c: In function 'init': ../src/main.c:82:5: error: initialization from incompatible pointer type [-Werror] .load = main_window_load, ^ ../src/main.c:82:5: error: (near initialization for '(anonymous).load') [-Werror] ``` To fix this, double check that the implementation provided has the same return type and argument list as specified in the API documentation. --- # Debugging Source: https://developer.repebble.com/guides/debugging/ When writing apps, everyone makes mistakes. Sometimes a simple typo or omission can lead to all kinds of mysterious behavior or crashes. The guides in this section are aimed at trying to help developers identify and fix a variety of issues that can arise when writing C code (compile-time) or running the compiled app on Pebble (runtime). There are also a few strategies outlined here, such as app logging and other features of the `pebble` [_Command Line Tool_](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) that can indicate the source of a problem in the vast majority of cases. ## Contents - [**Common Runtime Errors**](https://developer.repebble.com/guides/debugging/common-runtime-errors.md) - Examples of commonly encountered runtime problems that cannot be detected at compile time and can usually be fixed by logical thought and experimentation. - [**Common Syntax Errors**](https://developer.repebble.com/guides/debugging/common-syntax-errors.md) - Details of common problems encountered when writing C apps for Pebble, and how to resolve them. - [**Debugging Alloy Apps with xsbug**](https://developer.repebble.com/guides/debugging/debugging-alloy-with-xsbug.md) - Use the xsbug JavaScript debugger to set breakpoints and inspect your Alloy app while it runs on the watch. - [**Debugging with App Logs**](https://developer.repebble.com/guides/debugging/debugging-with-app-logs.md) - How to use the app logs to debug problems with an app, as well as tips on interpreting common run time errors. - [**Debugging with GDB**](https://developer.repebble.com/guides/debugging/debugging-with-gdb.md) - How to use GDB to debug a Pebble app in the emulator. --- # Debugging Alloy Apps with xsbug Source: https://developer.repebble.com/guides/debugging/debugging-alloy-with-xsbug/ Alloy apps run on Moddable's XS JavaScript engine, which ships with a graphical source-level debugger called **xsbug**. With xsbug you can set breakpoints, step through JavaScript, inspect variables, and watch `console.log` / `trace` output while your app runs on a watch or in the emulator. > **Platform Support** : Alloy and xsbug debugging are available on Emery (Pebble Time 2) and Gabbro (Pebble Round 2). This requires `pebble-tool` 5.0.38 or later. ## Building a Debug Build xsbug can only attach to an app that was built in debug mode. Build your project with the `--debug` flag: ``` $ pebble build --debug ``` A debug build: - compiles your C code without optimizations (`-O0`) for easier stepping; - defines `PBL_DEBUG`, which the Alloy entry point (`src/c/mdbl.c`) uses to set `kModdableCreationFlagDebug` and enable the xsbug connection; and - produces a separate `_debug.pbw` bundle, so your debug and release builds can coexist. > **Note** : A debug build is larger and runs more slowly than a release build, and may not fit on memory-constrained apps. Build without `--debug` for normal testing and release. ## Launching the Debugger When you install a `--debug` build, the Pebble tool automatically launches xsbug, opens your `src/embeddedjs` folder so breakpoints map to your source, and bridges the JavaScript debugger over the connection _before_ the app boots - so the debugger is attached as soon as your code starts running: ``` $ pebble install --emulator emery Launching xsbug JavaScript debugger... ``` The same happens for `pebble logs`, which also prefers the `_debug` bundle when one is present. xsbug ships in the active SDK's `moddable-tools` directory and is started for you. If the tool can't find it, it prints a warning - launch xsbug manually and re-run the install so the debugger can connect. ## Using xsbug Once connected, xsbug shows your running app. From its interface you can: - set and clear **breakpoints** by clicking in the gutter of a source file; - **step** over, into, and out of functions, and continue execution; - inspect the **call stack** , **local variables** , and **global** state; and - view **`console.log`** and **`trace`** output in the messages pane. For a full tour of the debugger, see the [Moddable xsbug documentation](https://www.moddable.com/documentation/xs/xsbug). ## See Also - [_Debugging with App Logs_](https://developer.repebble.com/guides/debugging/debugging-with-app-logs.md) - viewing log output - [_Getting Started with Alloy_](https://developer.repebble.com/guides/alloy/getting-started.md) - getting started with Alloy --- # Debugging with App Logs Source: https://developer.repebble.com/guides/debugging/debugging-with-app-logs/ When apps in development do not behave as expected the developer can use app logs to find out what is going wrong. The C SDK and PebbleKit JS can both output messages and values to the console to allow developers to get realtime information on the state of their app. This guide describes how to log information from both the C and JS parts of a watchapp or watchface and also how to read that information for debugging purposes. ## Logging in C The C SDK includes the [`APP_LOG()`](https://developer.repebble.com/docs/c/Foundation/Logging.md#app_log "app\_log") macro function which allows an app to log a string containing information to the console: ``` static int s\_buffer[5];for(int i = 0; i \< 10; i++) {// Store loop value in arrays\_buffer[i] = i;APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Loop index now %d", i);} ``` This will result in the following output before crashing: ``` [INFO] D main.c:20 Loop index now 0 [INFO] D main.c:20 Loop index now 1 [INFO] D main.c:20 Loop index now 2 [INFO] D main.c:20 Loop index now 3 [INFO] D main.c:20 Loop index now 4 ``` In this way it will be possible to tell the state of the loop index value if the app encounters a problem and crashes (such as going out of array bounds in the above example). ## Logging in JS Information can be logged in PebbleKit JS and Pebble.js using the standard JavaScript console, which will then be passed on to the log output view. An example of this is to use the optional callbacks when using `Pebble.sendAppMessage()` to know if a message was sent successfully to the watch: ``` console.log('Sending data to Pebble...');Pebble.sendAppMessage({'KEY': value}, function(e) {console.log('Send successful!');}, function(e) {console.log('Send FAILED!');}); ``` ## Viewing Log Data When viewing app logs, both the C and JS files' output are shown in the same view. The `pebble` [_Command Line Tool_](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) will output any logs from C and JS files after executing the `pebble logs` command and supplying the phone's IP address: ``` pebble logs --phone=192.168.1.25 ``` > Note: You can also use `pebble install --logs' to combine both of these operations into one command. ## Memory Usage Information In addition to the log output from developer apps, statistics about memory usage are also included in the C app logs when an app exits: ``` [INFO] process_manager.c:289: Heap Usage for App compass-ex: Total Size <22980B> Used <164B> Still allocated <0B> ``` This piece of information reports the total heap size of the app, the amount of memory allocated as a result of execution, and the amount of memory still allocated when it exited. This last number can alert any forgotten deallocations (for example, forgetting [`window_destroy()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_destroy "window\_destroy") after [`window_create()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_create "window\_create")). A small number such as `28B` is acceptable, provided it remains the same after subsequent executions. If it increases after each app exit it may indicate a memory leak. For more information on system memory usage, checkout the [Size presentation from the 2014 Developer Retreat](https://www.youtube.com/watch?v=8tOhdUXcSkw). ## Alloy Instrumentation Logging Alloy apps can enable instrumentation logging to get detailed output from the JavaScript runtime. This is useful for profiling and understanding what the XS engine is doing under the hood. To enable instrumentation logging, modify the `src/c/mdbl.c` file in your Alloy project. Instead of passing `NULL` to `moddable_createMachine()`, pass a [`ModdableCreationRecord`](https://developer.repebble.com/docs/c/Foundation/Alloy.md#ModdableCreationRecord "ModdableCreationRecord") with the `kModdableCreationFlagLogInstrumentation` flag: ``` #include \int main(void) {Window \*w = window\_create();window\_stack\_push(w, true);ModdableCreationRecord creation = {.recordSize = sizeof(ModdableCreationRecord),.flags = kModdableCreationFlagLogInstrumentation,};moddable\_createMachine(&creation);window\_destroy(w);} ``` The key changes from the default `mdbl.c` are: 1. Create a [`ModdableCreationRecord`](https://developer.repebble.com/docs/c/Foundation/Alloy.md#ModdableCreationRecord "ModdableCreationRecord") struct, setting `recordSize` to `sizeof(ModdableCreationRecord)` for version compatibility. 2. Set `flags` to `kModdableCreationFlagLogInstrumentation`. 3. Pass a pointer to the creation record to `moddable_createMachine()` instead of `NULL`. The instrumentation output will appear alongside regular app logs. > **Note** : Instrumentation logging adds overhead and produces verbose output. Disable it for release builds by reverting to `moddable_createMachine(NULL)`. ## Avoid Excessive Logging As noted in the [API documentation](https://developer.repebble.com/docs/c/Foundation/Logging.md "Logging"), logging over Bluetooth can be a power-hungry operation if an end user has the Developer Connection enabled and is currently viewing app logs. In addition, frequent (multiple times per second) logging can interfere with frequent use of [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"), as the two mechanisms share the same channel for communication. If an app is logging sent/received AppMessage events or values while doing this sending, it could experience slow or dropped messages. Be sure to disable this logging when frequently sending messages. --- # Debugging with GDB Source: https://developer.repebble.com/guides/debugging/debugging-with-gdb/ As of SDK 3.10 (and [Pebble Tool](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) 4.2), developers can use the powerful [GDB](https://www.gnu.org/software/gdb/) debugging tool to find and fix errors in Pebble apps while they are running in an emulator. GDB allows the user to observe the state of the app at any point in time, including the value of global and local variables, as well as current function parameters and a backtrace. Strategically placing breakpoints and observing these values can quickly reveal the source of a bug. **Notice** GDB cannot be used to debug an app running on a real watch. ## Starting GDB To begin using GDB, start an emulator and install an app: ``` $ pebble install --emulator basalt ``` Once the app is installed, begin using GDB: ``` $ pebble gdb --emulator basalt ``` Once the `(gdb)` prompt appears, the app is paused by GDB for observation. To resume execution, use the `continue` (or `c`) command. Similarly, the app can be paused for debugging by pressing `control + c`. ``` (gdb) c Continuing. ``` A short list of useful commands (many more are available) can be also be obtained from the `pebble` tool. Read the [_Emulator Interaction_](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md#gdb) section of the [_Command Line Tool_](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) guide for more details on this list. ``` $ pebble gdb --help ``` ## Observing App State To see the value of variables and parameters at any point, set a breakpoint by using the `break` (or `b`) command and specifying either a function name, or file name with a line number. For example, the snippet below shows a typical [`TickHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler "TickHandler") implementation with line numbers in comments: ``` /\* 58 \*/static void tick\_handler(struct tm \*tick\_time, TimeUnits changed) {/\* 59 \*/int hours = tick\_time-\>tm\_hour;/\* 60 \*/int mins = tick\_time-\>tm\_min;/\* 61 \*//\* 62 \*/if(hours \< 10) {/\* 63 \*//\* other code \*//\* 64 \*/}/\* 65 \*/} ``` To observe the values of `hours` and `mins`, a breakpoint is set in this file at line 61: ``` (gdb) b main.c:61 Breakpoint 2 at 0x200204d6: file ../src/main.c, line 61. ``` > Use `info break` to see a list of all breakpoints currently registered. Each can be deleted with `delete n`, where `n` is the breakpoint number. With this breakpoint set, use the `c` command to let the app continue until it encounters the breakpoint: ``` $ c Continuing. ``` When execution arrives at the breakpoint, the next line will be displayed along with the state of the function's parameters: ``` Breakpoint 2, tick_handler (tick_time=0x20018770, units_changed=(SECOND_UNIT | MINUTE_UNIT)) at ../src/main.c:62 62 if(hours < 10) { ``` The value of `hours` and `mins` can be found using the `info locals` command: ``` (gdb) info locals hours = 13 mins = 23 ``` GDB can be further used here to view the state of variables using the `p` command, such as other parts of the `tm` object beyond those being used to assign values to `hours` and `mins`. For example, the day of the month: ``` (gdb) p tick_time->tm_mday $2 = 14 ``` A backtrace can be generated that describes the series of function calls that got the app to the breakpoint using the `bt` command: ``` (gdb) bt #0 segment_logic (this=0x200218a0) at ../src/drawable/segment.c:18 #1 0x2002033c in digit_logic (this=0x20021858) at ../src/drawable/digit.c:141 #2 0x200204c4 in pge_logic () at ../src/main.c:29 #3 0x2002101a in draw_frame_update_proc (layer=, ctx=) at ../src/pge/pge.c:190 #4 0x0802627c in ?? () #5 0x0805ecaa in ?? () #6 0x0801e1a6 in ?? () #7 0x0801e24c in app_event_loop () #8 0x2002108a in main () at ../src/pge/pge.c:34 #9 0x080079de in ?? () #10 0x00000000 in ?? () ``` > Lines that include '??' denote a function call in the firmware. Building the app with `pebble build --debug` will disable some optimizations and can produce more readable output from GDB. However, this can increase code size which may break apps that are pushing the heap space limit. ## Fixing a Crash When an app is paused for debugging, the developer can manually advance each statement and precisely follow the path taken through the code and observe how the state of each variable changes over time. This is very useful for tracking down bugs caused by unusual input to functions that do not adequately check them. For example, a `NULL` pointer. The app code below demonstrates a common cause of an app crash, caused by a misunderstanding of how the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") stack works. The [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is created in the `.load` handler, but this is not called until the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is pushed onto the stack. The attempt to set the time to the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") by calling `update_time()` before it is displayed will cause the app to crash. ``` #include \static Window \*s\_window;static TextLayer \*s\_time\_layer;static void window\_load(Window \*window) {Layer \*window\_layer = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(window\_layer);s\_time\_layer = text\_layer\_create(bounds);text\_layer\_set\_text(s\_time\_layer, "00:00");text\_layer\_set\_text\_alignment(s\_time\_layer, GTextAlignmentCenter);layer\_add\_child(window\_layer, text\_layer\_get\_layer(s\_time\_layer));}static void update\_time() {time\_t now = time(NULL);struct tm \*tick\_time = localtime(&now);static char s\_buffer[8];strftime(s\_buffer, sizeof(s\_buffer), "%H:%M", tick\_time);text\_layer\_set\_text(s\_time\_layer, s\_buffer);}static void init() {s\_window = window\_create();window\_set\_window\_handlers(s\_window, (WindowHandlers) {.load = window\_load});update\_time();window\_stack\_push(s\_window, true);}static void deinit() {window\_destroy(s\_window);}int main() {init();app\_event\_loop();deinit();} ``` Supposing the cause of this crash was not obvious from the order of execution, GDB can be used to identify the cause of the crash with ease. It is known that the app crashes on launch, so the first breakpoint is placed at the beginning of `init()`. After continuing execution, the app will pause at this location: ``` (gdb) b init Breakpoint 2 at 0x2002010c: file ../src/main.c, line 26. (gdb) c Continuing. Breakpoint 2, main () at ../src/main.c:41 41 init(); ``` Using the `step` command (or Enter key), the developer can step through all the statements that occur during app initialization until the crash is found (and the `app_crashed` breakpoint is encountered. Alternatively, `bt full` can be used after the crash occurs to inspect the local variables at the time of the crash: ``` (gdb) c Continuing. Breakpoint 1, 0x0804af6c in app_crashed () (gdb) bt full #0 0x0804af6c in app_crashed () No symbol table info available. #1 0x0800bfe2 in ?? () No symbol table info available. #2 0x0800c078 in ?? () No symbol table info available. #3 0x0804c306 in ?? () No symbol table info available. #4 0x080104f0 in ?? () No symbol table info available. #5 0x0804c5c0 in ?? () No symbol table info available. #6 0x0805e6ea in text_layer_set_text () No symbol table info available. #7 0x20020168 in update_time () at ../src/main.c:22 now = 2076 tick_time = s_buffer = "10:38\000\000" #8 init () at ../src/main.c:31 No locals. #9 main () at ../src/main.c:41 No locals. #10 0x080079de in ?? () No symbol table info available. #11 0x00000000 in ?? () No symbol table info available. ``` The last statement to be executed before the crash is a call to `text_layer_set_text()`, which implies that one of its input variables was bad. It is easy to determine which by printing local variable values with the `p` command: ``` Breakpoint 4, update_time () at ../src/main.c:22 22 text_layer_set_text(s_time_layer, s_buffer); (gdb) p s_time_layer $1 = (TextLayer *) 0x0 <__pbl_app_info> ``` In this case, GDB displays `0x0` (`NULL`) for the value of `s_time_layer`, which shows it has not yet been allocated, and so will cause `text_layer_set_text()` to crash. And thus, the source of the crash has been methodically identified. A simple fix here is to swap `update_time()` and [`window_stack_push()`](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_push "window\_stack\_push") around so that `init()` now becomes: ``` static void init() {// Create a Windows\_window = window\_create();window\_set\_window\_handlers(s\_window, (WindowHandlers) {.load = window\_load});// Display the Windowwindow\_stack\_push(s\_window, true);// Set the timeupdate\_time();} ``` In this new version of the code the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") will be pushed onto the stack, calling its `.load` handler in the process, and the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") will be allocated and available for use once execution subsequently reaches `update_time()`. --- # Creating Pebble Packages Source: https://developer.repebble.com/guides/pebble-packages/creating-packages/ ## Getting Started To get started creating a package, run `pebble new-package some-name`. Make `some-name` something meaningful and unique; it is what you'll be publishing it under. You can check if it's taken on [npm](https://npmjs.org). If you'll be including a JavaScript component, you can add `--javascript` for a sample javascript file. ## Components of a Package ### C code **Notice** **Tip** : If you want to use an [`Event Service`](https://developer.repebble.com/docs/c/Foundation/Event_Service.md "Event Service"), you should use the [pebble-events](https://www.npmjs.com/package/pebble-events) package to handle subscriptions from multiple packages. Packages can export C functions to their consumers, and the default package exports `somelib_find_truth` as an example. To export a function, it simply has to be declared in a C file in `src/c/`, and declared in a header file in `include/`. For instance: `src/c/somelib.c`: ``` #include \#include "somelib.h"bool somelib\_find\_truth(void) {return true;} ``` `include/somelib.h`: ``` #pragma oncebool somelib\_find\_truth(void); ``` Notice that `include/` is already in the include path when building packages, so include files there can be included easily. By convention, packages should prefix their non-static functions with their name (in this case `somelib`) in order to avoid naming conflicts. If you don't want to export a function, don't include it in a file in `includes/`; you can instead use `src/c/`. You should still prefix any non-static symbols with your library name to avoid conflicts. Once the package is imported by a consumer — either an app or package — its include files will be in a directory named for the package, and so can be included with `#include `. There is no limit on the number or structure of files in the `include` directory. ### JavaScript code Packages can export JavaScript code for use in PebbleKit JS. The default JavaScript entry point for packages is always in `src/js/index.js`. However, files can also be required directly, according to [standard node `require` rules](https://nodejs.org/api/modules.html). In either case they are looked up relative to their root in `src/js/`. JavaScript code can export functions by attaching them to the global `exports` object: `src/js/index.js`: ``` exports.addNumbers = function(a, b) {return a + b;}; ``` Because JavaScript code is scoped and namespaced already, there is no need to use any naming convention. ### Resources Packages can include resources in the same way as apps, and those resources can then be used by both the package and the app. They are included in `package.json` in [the same manner as they are for apps](https://developer.repebble.com/guides/app-resources.md). To avoid naming conflicts, packages should prefix their resource names with the package name, e.g. `SOMELIB_IMAGE_LYRA`. It's best practice to define an image resource using the package name as a prefix on the resource `name`: ``` "resources": {"media": [{"name": "MEDIA\_PACKAGE\_IMAGE\_01\_TINY","type": "bitmap","file": "images/01-tiny.png"},//...]} ``` Create a `publishedMedia` entry if you want to make the images available for [_AppGlance slices_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) or [_Timeline pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md). ``` "resources": {//..."publishedMedia": [{"name": "MEDIA\_PACKAGE\_IMAGE\_01","glance": "MEDIA\_PACKAGE\_IMAGE\_01\_TINY","timeline": {"tiny": "MEDIA\_PACKAGE\_IMAGE\_01\_TINY","small": "MEDIA\_PACKAGE\_IMAGE\_01\_SMALL","large": "MEDIA\_PACKAGE\_IMAGE\_01\_LARGE"}}]} ``` > Note: Do NOT assign an `id` when defining `publishedMedia` within packages, see [_Using Packages_](https://developer.repebble.com/guides/pebble-packages/using-packages.md). Resource IDs are not assigned until the package has been linked with an app and compiled, so `RESOURCE_ID_*` constants cannot be used as constant initializers in packages. To work around this, either assign them at runtime or reference them directly. It is also no longer valid to try iterating over the resource id numbers directly; you must use the name defined for you by the SDK. ### AppMessage Keys Libraries can use AppMessage keys to reduce friction when creating a package that needs to communicate with the phone or internet, such as a weather package. A list of key names can be included in `package.json`, under `pebble.messageKeys`. These keys will be allocated numbers at app build time. We will inject them into your C code with the prefix `MESSAGE_KEY_`, e.g. `MESSAGE_KEY_CURRENT_TEMP`. If you want to use multiple keys as an 'array', you can specify a name like `ELEMENTS[6]`. This will create a single key, `ELEMENTS`, but leave five empty spaces after it, for a total of six available keys. You can then use arithmetic to access the additional keys, such as `MESSAGE_KEY_ELEMENTS + 5`. To use arrays in JavaScript you will need to know the actual numeric values of your keys. They will exist as keys on an object you can access via `require('message_keys')`. For instance: ``` var keys = require('message\_keys');var elements = ['honesty', 'generosity', 'loyalty', 'kindness', 'laughter', 'magic'];var dict = {}for (var i = 0; i \< 6; ++i) {dict[keys.ELEMENTS + i] = elements[i];}Pebble.sendAppMessage(dict, successCallback, failureCallback); ``` ## Building and testing Run `pebble build` to build your package. You can install it in a test project using `pebble package install ../path/to/package`. Note that you will have to repeat both steps when you change the package. If you try symlinking the package, you are likely to run into problems building your app. ## Publishing Publishing a Pebble Package requires you to have an npm account. For your convenience, you can create or log in to one using `pebble package login` (as distinct from `pebble login`). Having done this once, you can use `pebble package publish` to publish a package. Remember to document your package! It isn't any use to anyone if they can't figure out how to use it. README.md is a good place to include some documentation. Adding extra metadata to package.json is also worthwhile. In particular, it is worth specifying: - `repository`: if your package is open source, the repo you can find it in. If it's hosted on github, `"username/repo-name"` works; otherwise a git URL. - `license`: the license the package is licensed under. If you're not sure, try [choosealicense.com](http://choosealicense.com). You will also want to include a copy of the full license text in a file called LICENSE. - `description`: a one-sentence description of the package. For more details on package.json, check out [npm's package.json documentation](https://docs.npmjs.com/getting-started/using-a-package.json). --- # Pebble Packages Source: https://developer.repebble.com/guides/pebble-packages/ It is very common to want to use some piece of common functionality that we do not provide directly in our SDK: for instance, show the weather or swap our colors in a bitmap. To provide this functionality, developers can create _Pebble Packages_, which provide developers with easy ways to share their code. ## Contents - [**Creating Pebble Packages**](https://developer.repebble.com/guides/pebble-packages/creating-packages.md) - How to create Pebble Packages - [**Using Pebble Packages**](https://developer.repebble.com/guides/pebble-packages/using-packages.md) - How to use Pebble Packages --- # Using Pebble Packages Source: https://developer.repebble.com/guides/pebble-packages/using-packages/ ## Getting started Using pebble packages is easy: 1. Find a package. We will have a searchable listing soon, but for now you can [browse the pebble-package keyword on npm](https://www.npmjs.com/browse/keyword/pebble-package). 2. Run `pebble package install pebble-somelib` to install pebble-somelib. 3. Use the package. It is possible to use _some_ standard npm packages. However, packages that depend on being run in node, or in a real web browser, are likely to fail. If you install an npm package, you can use it in the usual manner, as described below. ### C code Packages should document their specific usage. However, in general, for C packages you can include their headers and call them like so: ``` #include \int main() {somelib\_do\_the\_thing();} ``` All of the package's include files will be in a folder named after the package. Packages may have any structure inside that folder, so you are advised to read their documentation. **Notice** **Tip** : If you want to use an [`Event Service`](https://developer.repebble.com/docs/c/Foundation/Event_Service.md "Event Service"), you should use the [pebble-events](https://www.npmjs.com/package/pebble-events) package to avoid conflicting with handlers registered by packages. ### JavaScript code JavaScript packages are used via the `require` function. In most cases you can just `require` the package by name: ``` var somelib = require('pebble-somelib');somelib.doTheThing(); ``` ### Resources If the package you are using has included image resources, you can reference them directly using their `RESOURCE_ID_*` identifiers. ``` static GBitmap \*s\_image\_01;s\_image\_01 = gbitmap\_create\_with\_resource(RESOURCE\_ID\_MEDIA\_PACKAGE\_IMAGE\_01\_TINY); ``` ### Published Media If the package you are using has defined `publishedMedia` resources, you can either reference the resources using their resource identifier (as above), or you can create an alias within the `package.json`. The `name` you specify in your own project can be used to reference that `publishedMedia` item for AppGlances and Timeline pins, eg. `PUBLISHED_ID_` For example, if the package exposes the following `publishedMedia`: ``` "resources": {//..."publishedMedia": [{"name": "MEDIA\_PACKAGE\_IMAGE\_01","glance": "MEDIA\_PACKAGE\_IMAGE\_01\_TINY","timeline": {"tiny": "MEDIA\_PACKAGE\_IMAGE\_01\_TINY","small": "MEDIA\_PACKAGE\_IMAGE\_01\_SMALL","large": "MEDIA\_PACKAGE\_IMAGE\_01\_LARGE"}}]} ``` You could define the following `name` and `alias` with a unique `id` in your `package.json`: ``` "resources": {//..."publishedMedia": [{"name": "SHARED\_IMAGE\_01","id": 1,"alias": "MEDIA\_PACKAGE\_IMAGE\_01"}]} ``` You can then proceed to use that `name`, prefixed with `PUBLISHED_ID_`, within your code: ``` const AppGlanceSlice entry = (AppGlanceSlice) {.layout = {.icon = PUBLISHED\_ID\_SHARED\_IMAGE\_01,.subtitle\_template\_string = "message"}}; ``` --- # App Metadata Source: https://developer.repebble.com/guides/tools-and-resources/app-metadata/ ## Project Defaults After creating a new local SDK Pebble project, additional setup of the project behavior and metadata can be performed in the `package.json` file. By default, the `pebble new-project` command will create a template `package.json` metadata file, which can then be modified as required. The generated `package.json` metadata looks like this: ``` {"name": "example-app","author": "MakeAwesomeHappen","version": "1.0.0","keywords": ["pebble-app"],"private": true,"dependencies": {},"pebble": {"displayName": "example-app","uuid": "5e5b3966-60b3-453a-a83b-591a13ae47d5","sdkVersion": "3","enableMultiJS": true,"targetPlatforms": ["aplite","basalt","chalk"],"watchapp": {"watchface": false},"messageKeys": ["dummy"],"resources": {"media": []}}} ``` **Notice** Older projects might have an `appinfo.json` file instead of a `package.json`. You can run `pebble convert-project` to update these projects. > Note: The `uuid` (Universally Unique IDentifier) field should be unique (as the name implies) and always properly generated. To do this on Mac OS X or Linux, use the `uuidgen` tool or a web service. Due to format requirements, these should never be manually modified. ## Available Properties Options defined in `package.json` (Required fields shown in **bold** ) are listed in the table below: | Property | Type | Default Value | Description | | --- | --- | --- | --- | | **`pebble.uuid`** | UUID | Developer-defined | Unique identifier [(UUID v4)](http://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29) for the app. Generated by `pebble new-project`. | | **`name`** | String | Developer-defined | App's short name. This is only used by npm and can't contain any non-URL-safe characters. | | **`pebble.displayName`** | String | Developer-defined | App's long name. This is what the app will appear as on the appstore, phone and watch. | | **`author`** | String | `"MakeAwesomeHappen"` | Name of the app's developer. | | **`version`** | String | `"1.0.0"` | Version label for the app. Must use the format `major.minor.0`. | | **`pebble.sdkVersion`** | String | `"3"` | The major version of the SDK that this app is being written for. | | `pebble.targetPlatforms` | Array of strings | `["aplite", "basalt", "chalk"]` | Specify which platforms to build this app for. Read [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) for a list of available platforms. Defaults to all if omitted. | | `pebble.enableMultiJS` | Boolean | `true` | Enables [use of multiple JS files](https://developer.repebble.com/blog/2016/01/29/Multiple-JavaScript-Files.md) in a project with a CommonJS-style `require` syntax. | | **`pebble.watchapp`** | Object | N/A | Used to configure the app behavior on the watch. See below for an example object. | | **`.watchface`** | Boolean | `false` | Field of `watchapp`. Set to `false` to behave as a watchapp. | | `.hiddenApp` | Boolean | `false` | Field of `watchapp`. Set to `true` to prevent the app from appearing in the system menu. | | `.onlyShownOnCommunication` | Boolean | `false` | Field of `watchapp`. Set to `true` to hide the app unless communicated with from a companion app. | | `pebble.capabilities` | Array of strings | None | List of capabilities that the app requires. The supported capabilities are `location`, `configurable` (detailed in [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md)), and `health` (detailed in [_Pebble Health_](https://developer.repebble.com/guides/events-and-services/health.md)). | | `pebble.messageKeys` | Object | `["dummy"]` | Keys used for [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") and [`AppSync`](https://developer.repebble.com/docs/c/Foundation/AppSync.md "AppSync"). This is either a list of mapping of [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") keys. See [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) for more information. | | `pebble.resources.media` | Object | `[]` | Contains an array of all of the media resources to be bundled with the app (Maximum 256 per app). See [_App Resources_](https://developer.repebble.com/guides/app-resources.md) for more information. | | `pebble.resources.publishedMedia` | Object | | Used for [_AppGlance Slices_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) and [_Timeline Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md). See [Published Media](#published-media) for more information. | > Note: `hiddenApp` and `onlyShownOnCommunication` are mutually exclusive. `hiddenApp` will always take preference. ## Hidden Watchapp The example below shows an example `watchapp` object for a watchapp that is hidden until communicated with from a companion app. ``` "watchapp": {"watchface": false,"hiddenApp": false,"onlyShownOnCommunication": true} ``` ## Published Media Introduced in SDK 4.0, `publishedMedia` provides a mechanism for applications to specify image resources which can be used within [_AppGlance Slices_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) or [_Timeline Pins_](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md). The `publishedMedia` object allows your watchapp, Pebble mobile application and Pebble web services to determine which image resources to use, based on a static ID. Without this lookup table, the components would not be able to establish the correct resources to use after an update to the watchapp. Let's take a look at the structure of `publishedMedia` in more detail. We'll start by declaring some image resources which we can reference in `publishedMedia` later. In this example we have 'hot' and 'cold' icons, in 3 different sizes. ``` "resources": {"media": [{"name": "WEATHER\_HOT\_ICON\_TINY","type": "bitmap","file": "hot\_tiny.png"},{"name": "WEATHER\_HOT\_ICON\_SMALL","type": "bitmap","file": "hot\_small.png"},{"name": "WEATHER\_HOT\_ICON\_LARGE","type": "bitmap","file": "hot\_large.png"},{"name": "WEATHER\_COLD\_ICON\_TINY","type": "bitmap","file": "cold\_tiny.png"},{"name": "WEATHER\_COLD\_ICON\_SMALL","type": "bitmap","file": "cold\_small.png"},{"name": "WEATHER\_COLD\_ICON\_LARGE","type": "bitmap","file": "cold\_large.png"}]} ``` Next we declare the `publishedMedia`: ``` "resources": {// ....."publishedMedia": [{"name": "WEATHER\_HOT","id": 1,"glance": "WEATHER\_HOT\_ICON\_TINY","timeline": {"tiny": "WEATHER\_HOT\_ICON\_TINY","small": "WEATHER\_HOT\_ICON\_SMALL","large": "WEATHER\_HOT\_ICON\_LARGE"}}, {"name": "WEATHER\_COLD","id": 2,"glance": "WEATHER\_COLD\_ICON\_TINY","timeline": {"tiny": "WEATHER\_COLD\_ICON\_TINY","small": "WEATHER\_COLD\_ICON\_SMALL","large": "WEATHER\_COLD\_ICON\_LARGE"}}]} ``` As you can see above, we can declare multiple entries in `publishedMedia`. Let's look at the properties in more detail: - `name` - This must be a unique alias to reference each entry of the `publishedMedia` object. We use the `name` for the AppGlanceSlice icon or Timeline Pin icon, and the system will load the appropriate resource when required. The naming convention varies depending on where it's being used. For example, the AppGlance C API prefixes the name: `PUBLISHED_ID_WEATHER_HOT` but the other APIs use: `app://images/WEATHER_HOT`. - `id` - This must be a unique number within the set of `publishedMedia`. It must be greater than 0. - `glance` - Optional. This references the `resource` image which will be displayed if this `publishedMedia` is used as the `icon` of an [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice "AppGlanceSlice"). Size: 25x25. - `timeline` - Optional. This references the `resource` images which will be displayed if this `publishedMedia` is used for the various sizes of a timeline icon. All three sizes must be specified. Sizes: Tiny 25x25, Small 50x50, Large 80x80. > Your `publishedMedia` entry must include either `glance`, `timeline`, or both. If you specify `glance` and `timeline`, the `glance` must be the same resource as `timeline.tiny`. If you remove a `publishedMedia` entry, you must not re-use the ID. --- # Color Picker Tool Source: https://developer.repebble.com/guides/tools-and-resources/color-picker/ Click a hexagon in the color map to see its SDK constant and how to use it in your Pebble app. [Uncorrected](javascript:void(0)) [Sunlight](javascript:void(0)) | Name: | | | Sample: | | | HTML code: | `` | | Uncorrected HTML code: | `` | | SDK Constant: | `` | | Code (RGB): | `` | | Code (Hex): | `` | #### Example Code Segment --- # Command Line Tool Source: https://developer.repebble.com/guides/tools-and-resources/pebble-tool/ The Pebble SDK includes a command line tool called `pebble`. This tool allows developers to create new projects, build projects, install apps on a watch and debug them. ## Enabling the Developer Connection Most `pebble` commands allow interaction with a Pebble watch. This relies on a communication channel opened between the `pebble` tool on a computer and the Pebble mobile application on the phone. To use a physical watch, enable the [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) in the Pebble mobile application. This is not required when using the emulator. ## Connecting to a Pebble There are several connection types possible with the command line tool. These can be used with any command that involves a watch, for example `install`. Connect to a physical watch through the CloudPebble connection. This works from any network, and requires signing in with the same Pebble account used in the Pebble mobile application (see [`login`](#login)): ``` $ pebble login$ pebble install --cloudpebble ``` Using `--phone` without an IP address also uses the CloudPebble connection. Connect to a physical watch connected to a phone on the same Wi-Fi network, where `IP` is the address shown in the Pebble mobile application when the [LAN connection](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md#using-a-lan-connection) is enabled: ``` $ pebble install --phone IP ``` Connect to an already running QEMU instance available on the `HOST` and `PORT` provided. Add `--pypkjs` with `--platform PLATFORM` to also start the PebbleKit JS runtime for that platform: ``` $ pebble install --qemu HOST:PORT$ pebble install --qemu HOST:PORT --pypkjs --platform basalt ``` Connect directly to a watch using a local Bluetooth connection, where `SERIAL` is the path to the device file. On OS X, this is similar to `/dev/cu.PebbleTimeXXXX-SerialPo` or `/dev/cu.PebbleXXXX-SerialPortSe`: > Note: Replace 'XXXX' with the actual value for the watch in question. ``` $ pebble install --serial SERIAL ``` ## Configure the Pebble Tool Use the CloudPebble connection by default: ``` $ export PEBBLE_CLOUDPEBBLE=1 ``` Or save the IP address of the phone for the LAN connection in an environment variable: ``` $ export PEBBLE_PHONE=192.168.1.42 ``` > Note: Only set one of `PEBBLE_CLOUDPEBBLE` or `PEBBLE_PHONE`. When switching between the CloudPebble and LAN connections, `unset` the other variable, as it can take priority over the `--phone` or `--cloudpebble` argument. Save the default choice of emulator platform: ``` $ export PEBBLE_EMULATOR=aplite ``` Save the default instance of QEMU: ``` $ export PEBBLE_QEMU=localhost:12344 ``` ## Debugging Output Get up to four levels of `pebble` tool log verbosity. This is available for all commands: ``` # Minimum level of verbosity$ pebble install -v # Maximum logging verbosity$ pebble install -vvvv ``` ## Installing Watchapps In addition to interacting with a physical Pebble watch, the Pebble emulator included in the local SDK can be used to run and debug apps without any physical hardware. Build an app, then use `pebble install` with the `--emulator` flag to choose an emulator platform: `aplite`, `basalt`, `chalk`, `diorite`, `emery`, `flint` or `gabbro`. This allows developers to test out apps on all platforms before publishing to the Pebble appstore. For example, to emulate Pebble Time or Pebble Time Steel: ``` $ pebble install --emulator basalt ``` > Note: If no `--emulator` parameter is specified, the running emulator will be used. Two options apply to `--emulator`: `--sdk VERSION` launches the emulator with an installed SDK other than the active one, and `--vnc` starts a VNC server for the emulator display. With a suitable color app installed, the emulator will load and display it: ![](https://developer.repebble.com/assets/images/guides/publishing-tools/emulator.png) The Pebble physical buttons are emulated with the following mapping: | Button | Key | | --- | --- | | Back | `Q`, left arrow | | Up | `W`, up arrow | | Select | `S`, right arrow | | Down | `X`, down arrow | ## Pebble Timeline in the Emulator With the emulator, it is also possible to view any past and future pins on the Pebble timeline by pressing Up or Down from the main watchface screen: | ![](https://developer.repebble.com/assets/images/guides/publishing-tools/timeline-past.png) | ![](https://developer.repebble.com/assets/images/guides/publishing-tools/timeline-future.png) | | Timeline, past view | Timeline, future view | ## Commands This section summarizes the available commands that can be used in conjunction with the `pebble` tool, on the applicable SDK versions. Most are designed to interact with a watch with `--phone` or the emulator with `--emulator`. ### Project Management #### new-project ``` $ pebble new-project [--c | --alloy] [--simple] [--javascript] [--worker] [--ai] NAME ``` Create a new project called `NAME`. This will create a directory in the location the command is used, containing all essential project files. There are also a number of optional parameters that allow developers to choose features of the generated project to be created automatically: - `--simple` - Initializes the main `.c` file with a minimal app template. - `--javascript` - Creates a new application including a `./src/pkjs/index.js` file to quickly start an app with a PebbleKit JS component. Read [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) for more information. - `--worker` - Creates a new application including a ` ./worker_src/NAME_worker.c` file to quickly start an app with a background worker. Read [_Background Worker_](https://developer.repebble.com/guides/events-and-services/background-worker.md) for more information. - `--ai` - Adds instruction files for Claude Code and Cursor to the project, so an AI coding agent can build and test it. - `--alloy` - Creates a new [_Alloy_](https://developer.repebble.com/guides/alloy.md) project, with JavaScript that runs on the watch in `src/embeddedjs/`. The C-specific options above do not apply. - `--c` - Creates a C project. This is the default. #### new-package ``` $ pebble new-package [--javascript] NAME ``` Create a new Pebble package called `NAME`, for publishing a library to npm rather than an app. `--javascript` adds a `js` directory. Read [_Pebble Packages_](https://developer.repebble.com/guides/pebble-packages.md) for more information. #### build ``` $ pebble build [--debug] ``` Compile and build the project into a `.pbw` file that can be installed on a watch or the emulator. `--debug` builds without optimizations for easier debugging with `pebble gdb`. Any further arguments are passed to `waf`. #### install ``` $ pebble install [FILE] [--logs] [--force] [--throttle [SECONDS]] [--qemu_logs] ``` Install the current project to the watch connected to the phone with the given `IP` address, or to the emulator on the `PLATFORM`. See [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) for a list of available platforms. For example, the Aplite platform is specified as follows: ``` $ pebble install --emulator aplite ``` It is also possible to use this command to install a pre-compiled `.pbw` `FILE`. In this case, the specified file will be installed instead of the current project. > Note: A `FILE` parameter is not required if running `pebble install` from inside a buildable project directory. In this case, the `.pbw` package is found automatically in `./build`. - `--logs` - Display app logs after installing, as `pebble logs` does. - `--force` - Install even if the `.pbw` does not support the connected platform. - `--throttle [SECONDS]` - Slow down the transfer to avoid QEMU timeouts with large apps. Defaults to 0.004 seconds between packets when given without a value. - `--qemu_logs` - Show the QEMU serial log while installing on the emulator. #### clean ``` $ pebble clean ``` Delete all build files in `./build` to prepare for a clean build, which can resolve some state inconsistencies that may prevent the project from building. #### convert-project ``` $ pebble convert-project ``` Convert an existing Pebble project to the current SDK. > Note: This will only convert the project, the source code will still have to updated to match any new APIs. #### analyze-size ``` $ pebble analyze-size [ELF] [--summary] [--verbose] ``` Print the size of each section of the built app, to see how much of the platform's app memory the code and static data use. `ELF` defaults to the build output of the current project. `--summary` prints one line per section and `--verbose` adds a per-symbol breakdown. #### package ``` $ pebble package install [PACKAGE]$ pebble package uninstall PACKAGE$ pebble package login$ pebble package publish ``` Manage the [_Pebble Packages_](https://developer.repebble.com/guides/pebble-packages.md) a project depends on. `install` adds an npm package to `package.json` and installs it, `uninstall` removes one, `login` signs in to npm and `publish` publishes the current package to npm. #### publish ``` $ pebble publish [--release-notes TEXT] [--is-published] [--non-interactive] [--all-platforms] [--no-gif-all-platforms] ``` Build the project and upload it as a new release to the app's listing on the [Developer Dashboard](https://developer.repebble.com/dashboard). Requires `pebble login`. If the app does not have a listing yet, the command prompts for the details to create one. `--non-interactive` uses flags (`--name`, `--version`, `--description`, `--source`, `--category`, `--icon-small`, `--icon-large`, `--screenshots FILE...`) instead of prompts, for CI. By default the command captures a rollover GIF on the emulator for each platform the app supports before uploading; `--no-gif-all-platforms` skips this and `--all-platforms` captures static screenshots as well. Uploaded releases are published immediately; `--is-published` is accepted but has no effect in the current version. ### SDK Management #### sdk ``` $ pebble sdk list$ pebble sdk install VERSION$ pebble sdk activate VERSION$ pebble sdk uninstall [--keep-data] VERSION$ pebble sdk set-channel CHANNEL$ pebble sdk include-path PLATFORM [--sdk VERSION] ``` Manage the installed SDKs. `list` shows the installed SDKs and the versions available for download, `install` downloads and installs a version (`latest` for the newest), `activate` selects which installed SDK `pebble build` and the emulator use, and `uninstall` removes one, keeping its emulator data with `--keep-data`. `set-channel` selects the release channel that `list` and `install` read from. `include-path` prints the directory of SDK headers for `PLATFORM`, for editor and language server configuration. Several SDK versions can be installed side by side. The SDK version (4.x) is separate from the version of the `pebble` tool itself (5.x). ### Pebble Interaction #### logs ``` $ pebble logs ``` Continuously display app logs from the watch connected to the phone with the given `IP` address, or from a running emulator. App log output is colorized according to log level. This behavior can be forced on or off by specifying `--color` or `--no-color` respectively. #### screenshot ``` $ pebble screenshot [FILENAME] [--no-correction] [--no-open]$ pebble screenshot --all-platforms$ pebble screenshot --gif-all-platforms [--gif-fps FPS] ``` Take a screenshot of the watch connected to the phone with the given `IP` address, or from any running emulator. If provided, the output is saved to `FILENAME`. Color correction may be disabled by specifying `--no-correction`. The auto-opening of screenshots may also be disabled by specifying `--no-open`. `--all-platforms` takes a screenshot on the emulator for each platform the current project supports, building the project first only if `build/` has no `.pbw` yet; run `pebble build` before it to capture current code. `--gif-all-platforms` does the same but records a rollover GIF of the app on each platform, capped at `--gif-fps` frames per second (default 30). These are the images `pebble publish` uploads. #### ping ``` $ pebble ping ``` Send a `ping` to the watch connected to the phone with the given `IP` address, or to any running emulator. #### repl ``` $ pebble repl ``` Launch an interactive python shell with a `pebble` object to execute methods on, using the watch connected to the phone with the given `IP` address, or to any running emulator. #### send-app-message ``` $ pebble send-app-message [--int KEY=VALUE ...] [--uint KEY=VALUE ...] [--string KEY=VALUE ...] [--bytes KEY=HEX ...] [--bytes-file KEY=FILE ...] [--app-uuid UUID] ``` Send an [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") dictionary to the running watchapp, to test its inbox handler without a PebbleKit JS or companion app. Keys are integers. Each option takes one or more `KEY=VALUE` entries, for example `--int 1=42 2=43 --string 3=hello`. Give each option once; a repeated option replaces the earlier entries. `--app-uuid` selects the target app when it is not the current project. #### fw ``` $ pebble fw install FILE [--slot {0,1}]$ pebble fw install-lang FILE$ pebble fw coredump [FILENAME] [--fresh]$ pebble fw flash-logs --board BOARD$ pebble fw enter-prf ``` Firmware management for a connected watch. `install` installs a `.pbz` firmware bundle (`--slot` selects the slot for multi-slot bundles), `install-lang` installs a language pack, `coredump` downloads the most recent coredump from the watch (`--fresh` requires one that has not been read yet), `flash-logs` dumps the firmware's flash log for the given board, and `enter-prf` reboots the watch into the recovery firmware. #### data-logging ##### list ``` $ pebble data-logging list ``` List the current data logging sessions on the watch. ##### download ``` $ pebble data-logging download --session-id SESSION-ID FILENAME ``` Downloads the contents of the data logging session with the given ID (as given by `pebble data-logging list`) to the given filename. ##### disable-sends ``` pebble data-logging disable-sends ``` Disables automatically sending data logging to the phone. This enables downloading data logging information without the phone's interference. It will also suspend any other app depending on data logging, which includes some firmware functionality. You should remember to enable it once you are done testing. ##### enable-sends ``` pebble data-logging enable-sends ``` Re-enables automatically sending data logging information to the phone. ##### get-sends-enabled ``` pebble data-logging get-sends-enabled ``` Indicates whether data logging is automatically sent to the phone. Change state with `enable-sends` and `disable-sends`. ### Emulator Interaction > Note: If the emulator does not boot, keeps apps that were uninstalled or stays on one screen, run `pebble kill` to stop it and then `pebble wipe` to delete its stored data. The next `pebble install` starts the emulator with a fresh flash image. #### gdb ``` $ pebble gdb ``` Use [GDB](https://www.gnu.org/software/gdb/) to step through and debug an app running in an emulator. Some useful commands are displayed in a cheat sheet when run with `--help` and summarized in the table below: > Note: Emulator commands that rely on interaction with the emulator (e.g. `emu-app-config`) will not work while execution is paused with GDB. | Command | Description | | --- | --- | | ctrl-C | Pause app execution. | | `continue` or `c` | Continue app execution. The app is paused while a `(gdb)` prompt is available. | | ctrl-D or `quit` | Quit gdb. | | `break` or `b` | Set a breakpoint. This can be either a symbol or a position: - `b function_name` to break when entering a function. - `b file_name.c:45` to break on line 45 of `file_name.c`. | | `step` or `s` | Step forward one line. | | `next` or `n` | Step _over_ the current line, avoiding stopping for any functions it calls into. | | `finish` | Run forward until exiting the current stack frame. | | `backtrace` or `bt` | Print out the current call stack. | | `p [expression]` | Print the result of evaluating the given `expression`. | | `info args` | Show the values of arguments to the current function. | | `info locals` | Show local variables in the current frame. | | `bt full` | Show all local variables in all stack frames. | | `info break` | List break points (#1 is ``, and is inserted by the `pebble` tool). | | `delete [n]` | Delete breakpoint #n. | Read [_Debugging with GDB_](https://developer.repebble.com/guides/debugging/debugging-with-gdb.md) for help on using GDB to debug app. #### emu-control ``` $ pebble emu-control [--port PORT] ``` Send near real-time accelerometer and compass readings from a phone, tablet, or computer to the emulator. When run, a QR code will be displayed in the terminal containing the IP address the device should connect to. Scanning the code will open this address in the device's browser. The IP address itself is also printed in the terminal for manual entry. `PORT` may be specified in order to make bookmarking the page easier. ![qr-code](https://developer.repebble.com/assets/images/guides/publishing-tools/qr-code.png) After connecting, the device's browser will display a UI with two modes of operation. ![accel-ui](https://developer.repebble.com/assets/images/guides/publishing-tools/accel-ui.png) The default mode will transmit sensor readings to the emulator as they are recorded. By unchecking the 'Use built-in sensors?' checkbox, manual values for the compass bearing and each of the accelerometer axes can be transmitted to the emulator by manipulating the compass rose or axis sliders. Using these UI elements will automatically uncheck the aforementioned checkbox. The values shown in bold are the Pebble SDK values, scaled relative to [`TRIG_MAX_ANGLE`](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_ANGLE "TRIG\_MAX\_ANGLE") in the case of the compass bearing value, and in the range of +/-4000 (+/- 4g) for the accelerometer values. The physical measurements with associated units are shown beside in parentheses. #### emu-app-config ``` $ pebble emu-app-config [--file FILE] ``` Open the app configuration page associated with the running app, if any. Uses the HTML configuration `FILE` if provided. See [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) to learn about emulator- specific configuration behavior. #### emu-tap ``` $ pebble emu-tap [--direction DIRECTION] ``` Send an accelerometer tap event to any running emulator. `DIRECTION` can be one of `x+`, `x-`, `y+`, `y-`, `z+`, or `z-`. #### emu-bt-connection ``` $ pebble emu-bt-connection --connected STATE ``` Send a Bluetooth connection event to any running emulator. `STATE` can be either `yes` or `no` for connected and disconnected events respectively. > Note: The disconnected event may take a few seconds to occur. #### emu-compass ``` $ pebble emu-compass --heading BEARING [--uncalibrated | --calibrating | --calibrated] ``` Send a compass update event to any running emulator. `BEARING` must be a number between `0` and `359` to be the desired compass bearing. Use any of `--uncalibrated`, `--calibrating`, or `--calibrated` to set the calibration state. #### emu-battery ``` $ pebble emu-battery [--percent LEVEL] [--charging] ``` Send a battery update event to any running emulator. `LEVEL` must be a number between `0` and `100` to represent the new battery level. The presence of `--charging` will determine whether the charging cable is plugged in. #### emu-accel ``` $ pebble emu-accel DIRECTION [FILE] ``` Send accelerometer data events to any running emulator. `DIRECTION` can be any of `tilt-left`, `tilt-right`, `tilt-forward`, `tilt-back`, `gravity+x`, `gravity-x`, `gravity+y`, `gravity-y`, `gravity+z`, `gravity-z`, `none` and `custom`. If `custom` is selected, give a `FILE` with one line per reading of comma-separated x, y and z values. #### transcribe ``` $ pebble transcribe [message] [--error {connectivity,disabled,no-speech-detected}] ``` Run a server that will act as a transcription service. Run it before invoking the [`Dictation`](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") service in an app. If `message` is provided, the dictation will be successful and that message will be provided. If `--error` is provided, the dictation will fail with the given error. Exactly one of `message` and `--error` is required. ``` $ pebble transcribe "Hello, Pebble!" ``` #### kill ``` $ pebble kill [--force] ``` Kill both the Pebble emulator and phone simulator. `--force` sends `SIGKILL`. #### emu-time-format ``` pebble emu-time-format --format FORMAT ``` Set the time format of the emulator to either 12-hour or 24-hour format, with a `FORMAT` value of `12h` or `24h` respectively. #### emu-set-timeline-quick-view ``` $ pebble emu-set-timeline-quick-view STATE ``` Show or hide the Timeline Quick View system overlay. STATE can be `on` or `off`. #### emu-set-content-size ``` $ pebble emu-set-content-size SIZE ``` Set the system content size on the emulator, as the user does in _Settings_. `SIZE` can be `small`, `medium`, `large` or `x-large`. Read [_Content Size_](https://developer.repebble.com/guides/user-interfaces/content-size.md) for how apps respond to it. #### emu-set-time ``` $ pebble emu-set-time TIME [--utc] ``` Set the emulator's clock. `TIME` is either `HH:MM:SS` for a time today, or a Unix timestamp in seconds. `--utc` interprets `HH:MM:SS` as UTC rather than local time. #### emu-button ``` $ pebble emu-button {click,push,release} [BUTTON ...] [--duration MS] [--repeat N] [--interval MS] ``` Press buttons on the emulator from the command line. `click` presses and releases, `push` holds a button down and `release` lets it go. `BUTTON` is one or more of `back`, `up`, `select` and `down`; several buttons are pressed together. `--duration` sets the click length in milliseconds (default 100), `--repeat` the number of clicks (default 1) and `--interval` the gap between them (default 200). #### wipe ``` $ pebble wipe ``` Delete the data stored for the emulator of every platform in the current SDK, including the flash image that holds installed apps and settings and the phone simulator's PebbleKit JS storage, but not the logged in Pebble account. Run `pebble kill` first so that a running emulator does not keep the old data. To wipe **all** data for every SDK version and log out, specify `--everything`. ### Pebble Account Management #### login ``` $ pebble login [--status] [--no-open-browser] ``` Sign in to your Pebble account. The command opens a browser window to complete the sign in; `--no-open-browser` prints the URL instead, for use over SSH. Signing in is required for the CloudPebble connection (`--cloudpebble`) and for `pebble publish`. `--status` prints whether you are signed in and whether the account is linked to a Developer Dashboard account, then exits. #### logout ``` $ pebble logout ``` Logs out the currently logged in Pebble account from this instance of the command line tool. ### Pebble Timeline Interaction #### insert-pin ``` $ pebble insert-pin FILE [--id ID] [--app-uuid UUID] ``` Insert a JSON pin from `FILE` into the timeline of the connected watch or emulator. `-` reads the pin from standard input. `--id` supplies the pin `id` when the file has none; if both are given they must match. `--app-uuid` sets the pin's parent app when it is not the current project. #### delete-pin ``` $ pebble delete-pin --id ID ``` Delete a pin previously inserted with `insert-pin`, specifying the same pin `ID`. --- # Developer Connection Source: https://developer.repebble.com/guides/tools-and-resources/developer-connection/ In order to install apps and view logs on a physical watch from CloudPebble or the `pebble` tool, the Pebble mobile app must be set up to allow a connection from the computer to the watch. This is called the Developer Connection, and it speeds up development by letting you install apps directly from your development environment. The Developer Connection can work in two ways: - **CloudPebble connection** (default) - The phone connects to the CloudPebble proxy using your Pebble account. CloudPebble and the `pebble` tool reach the phone through the proxy by signing in to the same account. This works from anywhere, and the phone and computer do not need to be on the same network. - **LAN connection** - The phone runs a server on port 9000 that the `pebble` tool connects to directly using the phone's IP address. This requires the phone and computer to be on the same Wi-Fi network. The steps below are the same in the Android and iOS versions of the Pebble app. ## Enabling the Developer Connection In the Pebble mobile app: - Make sure you are signed in with your Pebble account. If not, go to _Settings_ \> _General_ and tap _Sign In - Pebble Account_. - Make sure your watch is connected, then open the _Devices_ tab. - Tap the three dot icon on your watch to open its menu, then enable the _Dev Connection_ toggle. - The menu will show _Connected to CloudPebble_ once the connection is enabled. ![](https://developer.repebble.com/assets/images/guides/publishing-tools/dev-connection-cloudpebble.png) While the Developer Connection is enabled, a small developer icon is shown next to the watch's model name in the _Devices_ tab. > Note: The _Dev Connection_ option is only shown while the watch is connected. A watch that is out of range, in a low-power state or still reconnecting does not show it; wait for the _Devices_ tab to report the watch as connected and open the menu again. It cannot be enabled while signed out, unless the LAN connection is turned on (see [below](#using-a-lan-connection)). The Developer Connection always goes through the phone. The watch's charging cable does not carry data, so apps cannot be installed or debugged over it. ## Using with CloudPebble - Sign in to [CloudPebble](https://cloudpebble.repebble.com) with the same Pebble account you use in the Pebble mobile app. - Open your project and open _Build & Run_ in the sidebar. The _Phone_ tab appears once the project has been built successfully. - Under _Run On_, select the _Phone_ tab and choose _New Cloud Dev Connection_. - Click _Install and Run_. Use _View app logs_ to see the logs from your app, and _Screenshot_ to take a screenshot of the watch. The _Phone_ tab offers three connection types. All three reach the phone through the CloudPebble proxy; they differ in how CloudPebble signs in to it. | Option | Signs in with | Use it when | | --- | --- | --- | | _New Cloud Dev Connection_ | Your Pebble account, the one CloudPebble is signed in with | Always, with the current Pebble mobile app. | | _Cloud Dev Connection_ | A GitHub account linked under _Settings_ \> _GitHub Integrations_ | Only with earlier versions of the Pebble mobile app, which signed in to the Developer Connection with GitHub. | | _Local Dev Connection_ | The token of a legacy Pebble account sign-in | Not with the current Pebble mobile app. | CloudPebble cannot connect to the phone's IP address directly, so the LAN connection described below is for the `pebble` tool only. ## Using with the `pebble` Tool - Sign in to your Pebble account. This opens a browser window to complete the sign in: ``` $ pebble login ``` - Use the `--cloudpebble` argument with any command that interacts with a watch. The tool will wait for the phone to connect to the CloudPebble proxy before continuing: ``` $ pebble install --cloudpebble $ pebble logs --cloudpebble ``` Using `--phone` without an IP address does the same thing. To use the CloudPebble connection by default, set the `PEBBLE_CLOUDPEBBLE` environment variable: ``` $ export PEBBLE_CLOUDPEBBLE=1 ``` ## Using a LAN Connection The LAN connection lets the `pebble` tool connect to the phone directly over your local network, without signing in. CloudPebble does not offer a way to enter the phone's IP address. Its _Local Dev Connection_ option still goes through the cloud proxy, so use the `pebble` tool for the LAN connection. > Note: The LAN connection is not authenticated. Anyone on the same network can install apps on your watch while it is enabled, so only use it on trusted networks. In the Pebble mobile app: - Open the _Settings_ tab, select _Phone_ at the top, and tap _Connectivity_. ![](https://developer.repebble.com/assets/images/guides/publishing-tools/dev-connection-settings.png) - Enable _Use LAN developer connection_. ![](https://developer.repebble.com/assets/images/guides/publishing-tools/dev-connection-lan-setting.png) - Go back to the _Devices_ tab, open your watch's menu and enable the _Dev Connection_ toggle. If it was already enabled, toggle it off and on again. - Make note of the _IPv4_ address shown under the toggle. ![](https://developer.repebble.com/assets/images/guides/publishing-tools/dev-connection-lan.png) Use the IP address with the `--phone` argument: ``` $ pebble install --phone 192.168.1.42$ pebble logs --phone 192.168.1.42 ``` The server always uses port 9000. To avoid typing the IP address each time, save it in an environment variable: ``` $ export PEBBLE_PHONE=192.168.1.42 ``` > Note: Only set one of `PEBBLE_CLOUDPEBBLE` or `PEBBLE_PHONE`. When switching between the CloudPebble and LAN connections, `unset` the other variable, as it can take priority over the `--phone` or `--cloudpebble` argument. ### Connecting over USB on Android On Android, the LAN connection can also be used over USB with `adb`, which works even when the phone and computer are on different networks. With the phone connected over USB and the Pebble app running, enable the LAN connection and forward port 9000 to the computer: ``` $ adb shell am broadcast -a coredevices.coreapp.DEV_CONNECTION \ -n coredevices.coreapp/coredevices.coreapp.debug.DevConnectionReceiver$ adb forward tcp:9000 tcp:9000$ pebble install --phone 127.0.0.1 ``` ## Working Without Internet Access The CloudPebble connection needs the phone and the computer to reach the proxy on the internet. Without internet access, use the LAN connection with `--phone` and the phone's IP address. The phone and computer only need to be on the same Wi-Fi network, for example a hotspot started on the phone. On Android, the USB method above works with no network at all. The `--serial` argument of the `pebble` tool connects to a watch over a Bluetooth serial port on the computer. It only works with watches that support Bluetooth Classic. Pebble 2 Duo, Pebble Time 2 and Pebble Round 2 use Bluetooth LE only, so `--serial` cannot be used with them. ## Limitations Inserting timeline pins with `pebble insert-pin` is not currently supported over the Developer Connection. --- # Hardware Information Source: https://developer.repebble.com/guides/tools-and-resources/hardware-information/ The Pebble watch family comprises of multiple generations of hardware, each with unique sets of features and capabilities. Developers wishing to reach the maximum number of users will want to account for these differences when developing their apps. The table below details the differences between hardware platforms: | Company | Pebble Technology Corporation | Core Devices | | --- | --- | --- | | Model | Classic, Steel | Time, Time Steel | Time Round | Pebble 2 | Pebble 2 Duo | Pebble Time 2\* | Pebble Round 2 | | --- | --- | --- | --- | --- | --- | --- | --- | | Platform | Aplite | Basalt | Chalk | Diorite | Flint | Emery | Gabbro | | SOC | [STMicro STM32F205RE](https://www.st.com/en/microcontrollers-microprocessors/stm32f205re.html) | [STMicro STM32F411](https://www.st.com/en/microcontrollers-microprocessors/stm32f411.html) | [Nordic nRF52840](https://www.nordicsemi.com/Products/nRF52840) | [SiFli SF32LB52J](https://www.sifli.com/en/node/31) | | CPU | Cortex-M3 64 MHz | Cortex-M4 100 MHz | Cortex-M4 64 MHz | [Star-MC1](https://www.armchina.com/mountain?infoId=160&name=%22%E6%98%9F%E8%BE%B0%22%20STAR-MC1&CategoryStr=%22%E6%98%9F%E8%BE%B0%22&pStatus=) (Cortex-M33-like) 240 MHz | | Max Resource Size | 96k | 256k | | Max App Size (code + heap) | 24k | 64k | 128k | | Display Shape | Rectangle | Round | Rectangle | Round | | Display Size | 1.26" | 1.25" | 1" | 1.26" | 1.5" | 1.3" | | Display Resolution | 144 x 168 | 180 x 180 | 144 x 168 | 200 x 228 | 260 x 260 | | Display PPI | 175 | 182 | 175 | 202 | 200 | | Display Colors | 2 _(B/W)_ | 64 _(Color)_ | 2 _(B/W)_ | 64 _(Color)_ | | Display Manufacturer | Sharp _(LS013B7DH05)_ | JDI _(Color)_ | JDI _(Color)_ | Sharp _(LS013B7DH05)_ | JDI _(LPM015M135A)_ | Sharp _(LS013B7DD02)_ | | Touch Screen | No | Yes | | Backlight | White LED | Multicolor RGB LED | White LED | | Heart Rate Monitor | No | No _(possible with smartstrap)_ | Yes _(except SE model)_ | No | Yes | No | | Microphone | No | Yes | Yes _(with 2nd Mic for ENC\*1)_ | | Speaker | No | Yes | No | | Sensors | Accelerometer, Compass | Accelerometer | 6-axis IMU, Compass, Barometer | 6-axis IMU, Compass | 3-axis IMU, Compass | | Buttons | 4 | | Vibration | ERM (eccentric rotating mass) | LRA (linear resonant actuator) | | Case Material | Polycarbonate _(P, PT)_ Stainless Steel 316 _(PS, PTS)_ | Stainless Steel 316 | Polycarbonate | Stainless Steel 316 | | Lens Material | Polycarbonate | Gorilla Glass _(curved)_ | Gorilla Glass _(flat)_ | Hardened Glass _(flat)_ | Hardened Glass _(curved)_ | | Charging Port | Power only | Smart accessory port | Smart accessory port _(data only)_ | Power only | | Battery Life\*4 | ~7 days | ~7 days (Pebble Time), ~10 days (Pebble Time Steel) | ~2 days | ~7 days | ~30 days | ~14 days | | Water Resistance\*3 | 5 ATM\*2 _(50 m)_ | 3 ATM\*2 _(30 m)_ | None _(Splash Resistant)_ | 3 ATM\*2 _(30 m)_ | 20 m | TBD _(target IPX8)_ | TBD _(target 30m)_ | | Model | Classic, Steel | Time, Time Steel | Time Round | Pebble 2 | Pebble 2 Duo | Pebble Time 2\* | Pebble Round 2 | | --- | --- | --- | --- | --- | --- | --- | --- | \*1 ENC = Environmental Noise Cancellation \*2 Please note regarding water resistance: - 3 ATM → Safe for hand-washing, rain, splashes, and brief immersion, but not for swimming/diving. - 5 ATM → Safe for swimming in shallow water, showering, and snorkeling, but not for diving. \*3Rated waterproofing is applicable to new, sealed hardware. Age or repairs may compromise water resistance. \*4Rated battery life is applicable to new hardware in typical usage conditions. Heavy use or use of power intensive apps and watchfaces may lead to a lower battery life. Battery life will reduce as the watch ages. Aftermarket replacement batteries may have a lower capacity. See [_Available Defines and Macros_](https://developer.repebble.com/guides/best-practices/building-for-every-pebble.md#available-defines-and-macros) for a complete list of compile-time defines available. ## Platform Support Each Pebble watch is identified by a platform name in the SDK: | Platform | Watches | | --- | --- | | `aplite` | Pebble, Pebble Steel | | `basalt` | Pebble Time, Pebble Time Steel | | `chalk` | Pebble Time Round | | `diorite` | Pebble 2 | | `flint` | Pebble 2 Duo | | `emery` | Pebble Time 2 | | `gabbro` | Pebble Round 2 | Not every language and feature is available on every platform: | Feature | Platforms | Notes | | --- | --- | --- | | C SDK | All | | | Alloy (JavaScript on the watch) | `emery`, `gabbro` | The firmware includes the Alloy runtime on `emery` and `gabbro` only, and the `pebble` tool templates target those two. CloudPebble also lists `flint`, but Alloy apps do not run on `flint` yet. `aplite`, `basalt`, `chalk` and `diorite` are not supported. See [_Alloy_](https://developer.repebble.com/guides/alloy.md). | | PebbleKit JS | All | Runs on the phone, in the Pebble mobile app. | | Touch | `emery`, `gabbro` | Watchapps only. Watchfaces cannot receive touch events. See [_Touch_](https://developer.repebble.com/guides/events-and-services/touch.md). | | Heart rate monitor | `diorite` (except Pebble 2 SE), `emery` | See [_Heart Rate Monitor_](https://developer.repebble.com/guides/events-and-services/hrm.md). | | Speaker | `flint`, `emery` | See [_Speaker_](https://developer.repebble.com/guides/events-and-services/speaker.md). | | Microphone | All except `aplite` | See [_Dictation_](https://developer.repebble.com/guides/events-and-services/dictation.md). | Watchfaces cannot receive button or touch events on any platform. --- # Internationalization Source: https://developer.repebble.com/guides/tools-and-resources/internationalization/ When distributing an app in the Pebble appstore, it can be downloaded by Pebble users all over the world. Depending on the app, this means that developers may want to internationalize their apps for a varierty of different locales. The locale is the region in the world in which the user may be using an app. The strings used in the app to convey information should be available as translations in locales where the app is used by speakers of a different language. This is the process of localization. For example, users in France may prefer to see an app in French, if the translation is available. ## Internationalization on Pebble The Pebble SDK allows an app to be localized with the [`Internationalization`](https://developer.repebble.com/docs/c/Foundation/Internationalization.md "Internationalization") API. Developers can use this to adjust how their app displays and behaves to cater for users in non-English speaking countries. By default all apps are assumed to be in English. Choose the user selected locale using [`i18n_get_system_locale()`](https://developer.repebble.com/docs/c/Foundation/Internationalization.md#i18n_get_system_locale "i18n\_get\_system\_locale") or to force one using [`setlocale()`](https://developer.repebble.com/docs/c/Standard_C/Locale.md#setlocale "setlocale"). > Note: The app's locale also affects [`strftime()`](https://developer.repebble.com/docs/c/Standard_C/Time.md#strftime "strftime"), which will output different strings for different languages. Bear in mind that these strings may be different lengths and use different character sets, so the app's layout should scale accordingly. ## Locales Supported By Pebble This is the set of locales that are currently supported: | Language | Region | Value returned by [`i18n_get_system_locale()`](https://developer.repebble.com/docs/c/Foundation/Internationalization.md#i18n_get_system_locale "i18n\_get\_system\_locale") | | --- | --- | --- | | English | United States | `"en_US"` | | French | France | `"fr_FR"` | | German | Germany | `"de_DE"` | | Spanish | Spain | `"es_ES"` | | Italian | Italy | `"it_IT"` | | Portuguese | Portugal | `"pt_PT"` | | Chinese | China | `"en_CN"` | | Chinese | Taiwan | `"en_TW"` | When the user's preferred language is set using the Pebble Time mobile app, the required characters will be loaded onto Pebble and will be used by the compatible system fonts. **This means that any custom fonts used should include the necessary locale-specific characters in order to display correctly.** ## Translating an App By calling `setlocale(LC_ALL, "")`, the app can tell the system that it supports internationalization. Use the value returned by [`setlocale()`](https://developer.repebble.com/docs/c/Standard_C/Locale.md#setlocale "setlocale") to translate any app strings. The implementation follows the [libc convention](https://www.gnu.org/software/libc/manual/html_node/Setting-the-Locale.html#Setting-the-Locale). Users may expect text strings, images, time display formats, and numbers to change if they use an app in a certain language. Below is a simple approach to providing a translation: ``` // Use selocale() to obtain the system locale for translationchar \*sys\_locale = setlocale(LC\_ALL, "");// Set the TextLayer's contents depending on localeif (strcmp("fr\_FR", sys\_locale) == 0) {text\_layer\_set\_text(s\_output\_layer, "Bonjour tout le monde!");} else if ( /\* Next locale string comparison \*/ ) {/\* other language cases... \*/} else {// Fall back to Englishtext\_layer\_set\_text(s\_output\_layer, "Hello, world!");} ``` The above method is most suitable for a few strings in a small number of languages, but may become unwieldly if an app uses many strings. A more streamlined approach for these situations is given below in [_Using Locale Dictionaries_](#using-locale-dictionaries). ## Using the Locale in JavaScript Determine the language used by the user's phone in PebbleKit JS using the standard [`navigator`](http://www.w3schools.com/jsref/prop_nav_language.asp) property: ``` console.log('Phone language is ' + navigator.language); ``` ``` [INFO] js-i18n-example__1.0/index.js:19 Phone language is en-US ``` This will reflect the user's choice of 'Language & Input' -\> 'Language' on Android and 'General' -\> 'Language & Region' -\> 'i[Pod or Phone] Language' on iOS. > Note: Changing the language on these platforms will require a force close of the Pebble app or device restart before changes take effect. ## Choosing the App's Locale It is also possible to explicitly set an app's locale using [`setlocale()`](https://developer.repebble.com/docs/c/Standard_C/Locale.md#setlocale "setlocale"). This is useful for providing a language selection option for users who wish to do so. This function takes two arguments; the `category` and the `locale` string. To set localization **only** for the current time, use `LC_TIME` as the `category`, else use `LC_ALL`. The `locale` argument accepts the ISO locale code, such as `"fr_FR"`. Alternatively, set `locale` to `NULL` to query the current locale or `""` (an empty string) to set the app's locale to the system default. ``` // Force the app's locale to Frenchsetlocale(LC\_ALL, "fr\_FR"); ``` To adapt the way an application displays time, call `setlocale(LC_TIME, "")`. This will change the locale used to display time and dates in [`strftime()`](https://developer.repebble.com/docs/c/Standard_C/Time.md#strftime "strftime") without translating any strings. ## Effect of Setting a Locale Besides returning the value of the current system locale, calling [`setlocale()`](https://developer.repebble.com/docs/c/Standard_C/Locale.md#setlocale "setlocale") will have a few other effects: - Translate month and day names returned by [`strftime()`](https://developer.repebble.com/docs/c/Standard_C/Time.md#strftime "strftime") `%a`, `%A`, `%b`, and `%B`. - Translate the date and time representation returned by [`strftime()`](https://developer.repebble.com/docs/c/Standard_C/Time.md#strftime "strftime") `%c`. - Translate the date representation returned by [`strftime()`](https://developer.repebble.com/docs/c/Standard_C/Time.md#strftime "strftime") `%x`. Note that currently the SDK will not change the decimal separator added by the `printf()` family of functions when the locale changes; it will always be a `.` regardless of the currently selected locale. ## Using Locale Dictionaries A more advanced method to include multiple translations of strings in an app (without needing a large number of `if`, `else` statements) is to use the [Locale Framework](https://github.com/pebble-hacks/locale_framework). This framework works by wrapping every string in the project's `src` directory in the `_()` and replacing it with with a hashed value, which is then used with the current locale to look up the translated string from a binary resource file for that language. Instructions for using this framework are as follows: - Add `hash.h`, `localize.h` and `localize.c` to the project. This will be the `src` directory in the native SDK. - Include `localize.h` in any file to be localized: ``` #include "localize.h" ``` - Initalize the framework at the start of the app's execution: ``` int main(void) {// Init locale frameworklocale\_init();/\*\* Other app setup code \*\*/} ``` - For every string in each source file to be translated, wrap it in `_()`: ``` text\_layer\_set\_text(some\_layer, \_("Meal application")); ``` - Save all source files, then run `get_dict.py` from the project root directory to get a JSON file containing the hashed strings to be translated. This file will look like the one below: ``` { "1204784839": "Breakfast Time", "1383429240": "Meal application", "1426781285": "A fine meal with family", "1674248270": "Lunch Time", "1753964326": "Healthy in a hurry", "1879903262": "Start your day right", "2100983732": "Dinner Time" } ``` - Create a copy of the JSON file and perform translation of each string, keeping the equivalent hash values the same. Name the file according to the language, such as `locale_french.json`. - Convert both JSON files into app binary resource files: ``` $ python dict2bin.py locale_english.json $ python dict2bin.py locale_french.json ``` - Add the output binary files as project resources as described in [_Raw Data Files_](https://developer.repebble.com/guides/app-resources/raw-data-files.md). When the app is compiled and run, the `_()` macro will be replaced with calls to `locale_str()`, which will use the app's locale to load the translated string out of the binary resource files. > Note: If the translation lookup fails, the framework will fall back to the English binary resource file. ## Publishing Localized Apps The appstore does not yet support localizing an app's resources (such as name, description, images etc), so use this guide to prepare the app itself. To cater for users viewing appstore listings in a different locale, include a list of supported languages in the listing description, which itself can be written in multiple languages. The format for a multi-lingual appstore description should display the supported languages at the top and a include a copy in each language, as shown in the example below: > Now supports - French, German and Spanish. > > This compass app allows you to navigate in any directions from your wrist! Simply open the app and tilt to show the layout you prefer. > > Français > > Cette application de la boussole vous permet de naviguer dans toutes les directions à partir de votre poignet! Il suffit d'ouvrir l'application et de l'inclinaison pour montrer la disposition que vous préférez. > > Deutsch > > Dieser Kompass App ermöglicht es Ihnen, in jeder Richtung vom Handgelenk zu navigieren! Öffnen Sie einfach die App und kippen, um das Layout Sie bevorzugen zu zeigen. > > Español > > Esta aplicación brújula le permite navegar en cualquier dirección de la muñeca ! Basta con abrir la aplicación y la inclinación para mostrar el diseño que prefiera. --- # Testing with the Pebble Mobile App Source: https://developer.repebble.com/guides/tools-and-resources/testing-with-the-mobile-app/ The `pebble` tool runs its own PebbleKit JS environment next to the emulator, which is enough for most development. Some things only the Pebble mobile app does: the phone-side settings screen that opens configuration pages, the appstore locker, timeline pins created by PebbleKit JS and the real JavaScript runtimes described in [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md#differences-in-the-pebble-mobile-app). To test those without a watch, the Pebble mobile app can connect to a PebbleOS emulator over TCP in place of a Bluetooth watch. This needs the PebbleOS emulator built from source, since the Pebble mobile app connects to the emulator's Pebble Protocol port and only one client can use that port at a time. The emulator started by `pebble install --emulator` is already attached to the `pebble` tool's own PebbleKit JS runtime, so it cannot be used. ## Running the PebbleOS Emulator Follow the build instructions in the [PebbleOS repository](https://github.com/coredevices/pebbleos) to build one of the `qemu_emery`, `qemu_flint` or `qemu_gabbro` boards, then start it: ``` $ pbl configure --board=qemu_emery$ pbl build$ pbl qemu ``` The emulator serves the Pebble Protocol on TCP port 12344 of the computer. The `pebble` tool connects to this port with `--qemu`, so apps can still be installed and their logs read from the command line while the phone is connected: ``` $ pebble install --qemu localhost:12344$ pebble logs --qemu localhost:12344 ``` > Note: Only one client can be attached to the emulator's port at a time. When the Pebble mobile app is connected, disconnect it before using `--qemu` from the `pebble` tool, or install apps from the phone instead. ## iOS Simulator The iOS version of the Pebble mobile app accepts the emulator address as a launch argument. This is only possible from `simctl` or Xcode, so it works in the iOS Simulator and not on an iPhone. Build the app from the [mobileapp repository](https://github.com/coredevices/mobileapp), install it in a booted simulator and launch it with: ``` $ xcrun simctl launch booted coredevices.coreapp -qemu 127.0.0.1:12344 ``` The simulator runs on the same computer as the emulator, so `127.0.0.1` is the emulator's address. ## Android On Android the emulator can be reached from a device or an emulator image connected with `adb`. Forward the emulator's port to the phone, then tell the app to add the emulator as a watch: ``` $ adb reverse tcp:12344 tcp:12344$ adb shell am broadcast -a coredevices.coreapp.ADD_QEMU_WATCH \ -n coredevices.coreapp/coredevices.coreapp.debug.QemuSetupReceiver \ --es host 127.0.0.1 --ei port 12344 ``` The broadcast is only accepted from `adb`; another app on the phone cannot send it. After the broadcast the emulator appears in the _Devices_ tab like a watch, and the app connects to it. ### Faking a Location Apps that use `navigator.geolocation` in PebbleKit JS need a location on the phone. On an Android device without GPS reception, or an Android emulator image, set a test location with `adb`. Repeat the last command periodically so the fix does not go stale: ``` $ adb shell appops set com.android.shell android:mock_location allow$ for p in gps fused; do adb shell cmd location providers add-test-provider $p adb shell cmd location providers set-test-provider-enabled $p true adb shell cmd location providers set-test-provider-location $p \ --location 37.4438,-122.1650 done ``` ## Sideloading Apps Opening a `.pbw` file on the phone installs it on the connected watch. On Android the Pebble mobile app registers for `.pbw`, `.pbl` and `.pbz` files, so a file received by email, downloaded in the browser or shared from another app opens in the Pebble mobile app. On iOS the app declares the same document types, so a `.pbw` can be opened from the Files app, Mail, AirDrop or a share sheet. The app is added to the locker with the version marked _(sideloaded)_, and it is installed from there like an appstore app. Sideloaded apps have no appstore listing, so `Pebble.getTimelineToken()` returns nothing, and `Pebble.getAccountToken()` and `Pebble.getWatchToken()` return different values from the ones the same app gets once it is published. Read [_Tokens_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md#tokens) for details. The simplest way to get a `.pbw` onto a phone during development is still the [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md), which installs the build directly from the `pebble` tool or CloudPebble. --- # Tools and Resources Source: https://developer.repebble.com/guides/tools-and-resources/ This section of the developer guides contains information and resources surrounding management of SDK projects themselves, as well as additional information on using the Pebble emulator and the local SDK command-line utility. Developers looking to add internationalization support can also find information and tools for that purpose here. ## Contents - [**App Metadata**](https://developer.repebble.com/guides/tools-and-resources/app-metadata.md) - Details of the metadata that describes the app, such as its name, resources and capabilities. - [**Color Picker Tool**](https://developer.repebble.com/guides/tools-and-resources/color-picker.md) - Preview all the colors available on Pebble with the associated SDK contants and HTML codes. - [**Command Line Tool**](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) - How to use the Pebble command line tool to build, debug, and emulate apps. - [**Developer Connection**](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) - How to enable and use the Pebble Developer Connection to install and debug apps directly from a computer. - [**Hardware Information**](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) - Details of the the capabilities of the various Pebble hardware platforms. - [**Internationalization**](https://developer.repebble.com/guides/tools-and-resources/internationalization.md) - How to localize an app to multiple languages. - [**Testing with the Pebble Mobile App**](https://developer.repebble.com/guides/tools-and-resources/testing-with-the-mobile-app.md) - How to connect the Pebble mobile app to the emulator to test PebbleKit JS, configuration pages and timeline pins without a watch, and how to sideload a .pbw file. --- # App Configuration (manual setup) Source: https://developer.repebble.com/guides/user-interfaces/app-configuration-static/ > This guide provides the steps to manually create an app configuration page. The preferred approach is to use [_Clay for Pebble_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) instead. Many watchfaces and apps in the Pebble appstore include the ability to customize their behavior or appearance through the use of a configuration page. This mechanism consists of an HTML form that passes the user's chosen configuration data to PebbleKit JS, which in turn relays it to the watchface or watchapp. The HTML page created needs to be hosted online, so that it is accessible to users via the Pebble application. If you do not want to host your own HTML page, you should follow the [_Clay guide_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) to create a local config page. App configuration pages are powered by PebbleKit JS. To find out more about PebbleKit JS, [_read the guide_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md). ## Adding Configuration For an app to be configurable, it must marked as 'configurable' in the app's [_`package.json`_](https://developer.repebble.com/guides/tools-and-resources/app-metadata.md)`capabilities` array. The presence of this value tells the mobile app to display a gear icon next to the app, allowing users to access the configuration page. ``` "capabilities": ["configurable"] ``` ## Choosing Key Values Since the config page must transmit the user's preferred options to the watchapp, the first step is to decide upon the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") keys defined in `package.json` that will be used to represent the chosen value for each option on the config page: ``` "messageKeys": ["BackgroundColor","ForegroundColor","SecondTick","Animations"] ``` These keys will automatically be available both in C on the watch and in PebbleKit JS on the phone. Each of these keys will apply to the appropriate input element on the config page, with the user's chosen value transmitted to the watchapp's [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") handler once the page is submitted. ## Showing the Config Page Once an app is marked as `configurable`, the PebbleKit JS component must implement `Pebble.openURL()` in the `showConfiguration` event handler in `index.js` to present the developer's HTML page when the user wants to configure the app: ``` Pebble.addEventListener('showConfiguration', function() {var url = 'http://example.com/config.html';Pebble.openURL(url);}); ``` ## Creating the Config Page The basic structure of an HTML config page begins with a template HTML file: > Note: This page will be plain and unstyled. CSS styling must be performed separately, and is not covered here. ``` \\\\Example Configuration\\\\This is an example HTML forms configuration page.\\\ ``` The various UI elements the user will interact with to choose their preferences must be placed within the `body` tag, and will most likely take the form of HTML `input` elements. For example, a text input field for each of the example color options will look like the following: ``` \Background Color\\Foreground Color\ ``` Other components include checkboxes, such as the two shown below for each of the example boolean options: ``` \Enable Second Ticks\\Show Animations\ ``` The final element should be the 'Save' button, used to trigger the sending of the user's preferences back to PebbleKit JS. ``` \ ``` ## Submitting Config Data Once the 'Save' button is pressed, the values of all the input elements should be encoded and included in the return URL as shown below: ``` \// Get a handle to the button's HTML elementvar submitButton = document.getElementById('submit\_button');// Add a 'click' listenersubmitButton.addEventListener('click', function() {// Get the config data from the UI elementsvar backgroundColor = document.getElementById('background\_color\_input');var foregroundColor = document.getElementById('foreground\_color\_input');var secondTickCheckbox = document.getElementById('second\_tick\_checkbox');var animationsCheckbox = document.getElementById('animations\_checkbox');// Make a data object to be sent, coercing value types to integersvar options = {'background\_color': parseInt(backgroundColor.value, 16),'foreground\_color': parseInt(foregroundColor.value, 16),'second\_ticks': secondTickCheckbox.checked == 'true' ? 1 : 0,'animations': animationsCheckbox.checked == 'true' ? 1 : 0};// Determine the correct return URL (emulator vs real watch)function getQueryParam(variable, defaultValue) {var query = location.search.substring(1);var vars = query.split('&');for (var i = 0; i \< vars.length; i++) {var pair = vars[i].split('=');if (pair[0] === variable) {return decodeURIComponent(pair[1]);}}return defaultValue || false;}var return\_to = getQueryParam('return\_to', 'pebblejs://close#');// Encode and send the data when the page closesdocument.location = return\_to + encodeURIComponent(JSON.stringify(options));});\ ``` > Note: Remember to use `encodeURIComponent()` and `decodeURIComponent()` to ensure the JSON data object is transmitted without error. ## Hosting the Config Page In order for users to access your configuration page, it needs to be hosted online somewhere. One potential free service to host your configuration page is Github Pages: [Github Pages](https://pages.github.com/) allow you to host your HTML, CSS and JavaScript files and directly access them from a special branch within your Github repo. This also has the added advantage of encouraging the use of version control. ## Relaying Data through PebbleKit JS When the user submits the HTML form, the page will close and the result is passed to the `webviewclosed` event handler in the PebbleKit JS `index.js` file: ``` Pebble.addEventListener('webviewclosed', function(e) {// Decode the user's preferencesvar configData = JSON.parse(decodeURIComponent(e.response));} ``` The data from the config page should be converted to the appropriate keys and value types expected by the watchapp, and sent via [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"): ``` // Send to the watchapp via AppMessagevar dict = {'BackgroundColor': configData.background\_color,'ForegroundColor': configData.foreground\_color,'SecondTick': configData.second\_ticks,'Animations': configData.animations};// Send to the watchappPebble.sendAppMessage(dict, function() {console.log('Config data sent successfully!');}, function(e) {console.log('Error sending config data!');}); ``` ## Receiving Config Data Once the watchapp has called [`app_message_open()`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_open "app\_message\_open") and registered an [`AppMessageInboxReceived`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived "AppMessageInboxReceived") handler, that handler will be called once the data has arrived on the watch. This occurs once the user has pressed the submit button. To obtain the example keys and values shown in this guide, simply look for and read the keys as [`Tuple`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple "Tuple") objects using the [`DictionaryIterator`](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator "DictionaryIterator") provided: ``` static void inbox\_received\_handler(DictionaryIterator \*iter, void \*context) {// Read color preferencesTuple \*bg\_color\_t = dict\_find(iter, MESSAGE\_KEY\_BackgroundColor);if(bg\_color\_t) {GColor bg\_color = GColorFromHEX(bg\_color\_t-\>value-\>int32);}Tuple \*fg\_color\_t = dict\_find(iter, MESSAGE\_KEY\_ForegroundColor);if(fg\_color\_t) {GColor fg\_color = GColorFromHEX(fg\_color\_t-\>value-\>int32);}// Read boolean preferencesTuple \*second\_tick\_t = dict\_find(iter, MESSAGE\_KEY\_SecondTick);if(second\_tick\_t) {bool second\_ticks = second\_tick\_t-\>value-\>int32 == 1;}Tuple \*animations\_t = dict\_find(iter, MESSAGE\_KEY\_Animations);if(animations\_t) {bool animations = animations\_t-\>value-\>int32 == 1;}// App should now update to take the user's preferences into accountreload\_config();} ``` Read the [_Communication_](https://developer.repebble.com/guides/communication.md) guides for more information about using the [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") API. If you're looking for a simpler option, we recommend using [_Clay for Pebble_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) instead. --- # Developer Guides Source: https://developer.repebble.com/guides/ This is the main page for the Developer Guides, containing a large number of resources for developing all kinds of Pebble watchapps. New and experienced developers can use these resources to help build their skill set when writing for Pebble. ## What's Here? To help guide towards the most relevant information, the developer guides are split up into the following sections. A complete list of every available guide is available in the [Table of Contents](https://developer.repebble.com/guides/toc.md). ### Create beautiful and engaging apps - [**App Resources**](https://developer.repebble.com/guides/app-resources.md) - Information on the many kinds of files that can be used inside Pebble apps. - [**Design and Interaction**](https://developer.repebble.com/guides/design-and-interaction.md) - Information on creating the best app user experiences and layout designs. - [**Graphics and Animations**](https://developer.repebble.com/guides/graphics-and-animations.md) - Information on using animations and drawing shapes, text, and images, as well as more advanced techniques. - [**User Interfaces**](https://developer.repebble.com/guides/user-interfaces.md) - How to build effective user interfaces. Includes information on events, persistent storage, background worker, wakeups and app configuration. ### Create apps that interact with the world - [**Alloy**](https://developer.repebble.com/guides/alloy.md) - Information on creating apps with JavaScript using the Alloy framework, based on Moddable. - [**Communication**](https://developer.repebble.com/guides/communication.md) - How to talk to the phone via PebbleKit with JavaScript and on Android or iOS. - [**Events and Services**](https://developer.repebble.com/guides/events-and-services.md) - How to get data from the onboard sensors and services including the accelerometer, compass, and microphone. - [**Pebble Timeline**](https://developer.repebble.com/guides/pebble-timeline.md) - How to use Pebble timeline to bring timely information to app users outside the app itself. - [**Plugins (Preview)**](https://developer.repebble.com/guides/plugins.md) - How a .pbw's JavaScript can provide data sources and actions to other apps through the Pebble mobile app, and how a watchapp reads them from PebbleKit JS. ### Build, refine, and debug apps - [**Building with AI Agents**](https://developer.repebble.com/guides/ai-agents.md) - How to give an AI coding agent the documentation, project instructions and emulator commands it needs to build a Pebble app. - [**Appstore Publishing**](https://developer.repebble.com/guides/appstore-publishing.md) - How to publish apps in the Pebble appstore from the Developer Dashboard, the pebble tool or CloudPebble. - [**Best Practices**](https://developer.repebble.com/guides/best-practices.md) - Information to help optimize apps and ensure a good user experience. - [**Debugging**](https://developer.repebble.com/guides/debugging.md) - How to find and fix common compilation and runtime problems in apps. - [**Pebble Packages**](https://developer.repebble.com/guides/pebble-packages.md) - How to create and use Pebble Packages - [**Tools and Resources**](https://developer.repebble.com/guides/tools-and-resources.md) - Information on all the software tools available when writing Pebble apps, as well as other resources. --- # Guides Table of Contents Source: https://developer.repebble.com/guides/toc/ ## [1. Alloy](https://developer.repebble.com/guides/alloy.md) Information on creating apps with JavaScript using the Alloy framework, based on Moddable. #### [Getting Started with Alloy](https://developer.repebble.com/guides/alloy/getting-started.md) Learn how to create your first Alloy app for Pebble. #### [Piu UI Framework](https://developer.repebble.com/guides/alloy/piu-guide.md) Build declarative user interfaces with the Piu framework. #### [Poco Graphics](https://developer.repebble.com/guides/alloy/poco-guide.md) Low-level graphics rendering with the Poco framework. #### [Sensors and Input](https://developer.repebble.com/guides/alloy/sensors-and-input.md) Access Pebble sensors and handle button input in Alloy apps. #### [Storage](https://developer.repebble.com/guides/alloy/storage.md) Persist data between app launches using localStorage, key-value storage, or files. #### [Networking](https://developer.repebble.com/guides/alloy/networking.md) Make HTTP requests and use WebSockets via the phone proxy. #### [App Messages](https://developer.repebble.com/guides/alloy/app-messages.md) Send and receive messages between the watch and the phone. #### [Watchfaces](https://developer.repebble.com/guides/alloy/watchfaces.md) Build custom watchfaces with Alloy. #### [Animations](https://developer.repebble.com/guides/alloy/animations.md) Create smooth animations using Timeline and easing functions. #### [Port (Custom Drawing)](https://developer.repebble.com/guides/alloy/port-drawing.md) Combine Piu's declarative UI with custom drawing using Port. #### [Advanced Networking](https://developer.repebble.com/guides/alloy/advanced-networking.md) Low-level HTTP and WebSocket clients for advanced networking needs. #### [Native Functions (FFI)](https://developer.repebble.com/guides/alloy/ffi.md) Call native C functions directly from JavaScript in your Alloy app using the Foreign Function Interface. #### [Dictation](https://developer.repebble.com/guides/alloy/dictation.md) Capture speech as text in Alloy apps using the dictation API. #### [Wakeups](https://developer.repebble.com/guides/alloy/wakeups.md) Schedule your Alloy app to launch at a future time using wakeups. #### [Vibration](https://developer.repebble.com/guides/alloy/vibration.md) Provide haptic feedback in Alloy apps using the vibration motor. #### [Device Info and App Events](https://developer.repebble.com/guides/alloy/device-info.md) Query watch and screen properties, respond to app lifecycle events, and control the backlight in Alloy apps. #### [Health](https://developer.repebble.com/guides/alloy/health.md) Read steps, sleep, activities and heart rate data from Pebble Health in Alloy apps. #### [Alloy Reference](https://developer.repebble.com/guides/alloy/reference.md) Where the Alloy APIs are documented, and the details that are not obvious from the other guides: font names, resource IDs, modules, memory and TypeScript. ## [2. Building with AI Agents](https://developer.repebble.com/guides/ai-agents.md) How to give an AI coding agent the documentation, project instructions and emulator commands it needs to build a Pebble app. ## [3. App Resources](https://developer.repebble.com/guides/app-resources.md) Information on the many kinds of files that can be used inside Pebble apps. #### [Animated Images](https://developer.repebble.com/guides/app-resources/animated-images.md) How to add animated image resources to a project in the APNG format, and display them in your app. #### [App Assets](https://developer.repebble.com/guides/app-resources/app-assets.md) A collection of assets for use as resources in Pebble apps. #### [Converting SVG to PDC](https://developer.repebble.com/guides/app-resources/converting-svg-to-pdc.md) How to create compatible SVG files using Inkscape and Illustrator. #### [Fonts](https://developer.repebble.com/guides/app-resources/fonts.md) How to use built-in system fonts, or add your own font resources to a project. #### [Images](https://developer.repebble.com/guides/app-resources/images.md) How to add image resources to a project and display them in your app. #### [Pebble Draw Command File Format](https://developer.repebble.com/guides/app-resources/pdc-format.md) The binary file format description for Pebble Draw Command Frames, Images and Sequences. #### [Platform-specific Resources](https://developer.repebble.com/guides/app-resources/platform-specific.md) How to include different resources for different platforms, as well as how to include a resource only on a particular platform. #### [Raw Data Files](https://developer.repebble.com/guides/app-resources/raw-data-files.md) How to add raw data resources to a project and read them in your app. #### [System Fonts](https://developer.repebble.com/guides/app-resources/system-fonts.md) A complete list of all the system fonts available for use in Pebble projects. ## [4. Appstore Publishing](https://developer.repebble.com/guides/appstore-publishing.md) How to publish apps in the Pebble appstore from the Developer Dashboard, the pebble tool or CloudPebble. #### [Preparing a Submission](https://developer.repebble.com/guides/appstore-publishing/preparing-a-submission.md) What a Pebble appstore listing is made of, and the sizes and formats required for screenshots, icons and banners. #### [Publishing an App](https://developer.repebble.com/guides/appstore-publishing/publishing-an-app.md) How to submit an app and later releases from the Developer Dashboard, the `pebble` tool or CloudPebble. #### [Importing Apps from Rebble](https://developer.repebble.com/guides/appstore-publishing/importing-from-rebble.md) How to bring apps published in the Rebble appstore into the Developer Dashboard and prove ownership of them. ## [5. Best Practices](https://developer.repebble.com/guides/best-practices.md) Information to help optimize apps and ensure a good user experience. #### [Building for Every Pebble](https://developer.repebble.com/guides/best-practices/building-for-every-pebble.md) How to write one app compatible with all Pebble smartwatches. #### [Conserving Battery Life](https://developer.repebble.com/guides/best-practices/conserving-battery-life.md) How to write an app to consume power as efficiently as possible. #### [Modular App Architecture](https://developer.repebble.com/guides/best-practices/modular-app-architecture.md) How to break up a complex app into smaller pieces for managablilty, modularity and reusability. ## [6. Communication](https://developer.repebble.com/guides/communication.md) How to talk to the phone via PebbleKit with JavaScript and on Android or iOS. #### [Advanced Communication](https://developer.repebble.com/guides/communication/advanced-communication.md) Details of communication tips and best practices for more advanced scenarios. #### [Datalogging](https://developer.repebble.com/guides/communication/datalogging.md) Information on how to collect data batches using the Datalogging API. #### [PebbleKit Android](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) How to use PebbleKit to communicate with a watchapp on Android. #### [PebbleKit JS](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) How to use PebbleKit JS to communicate with the connected phone's JS environment, and how the Pebble mobile app runs it on Android and iOS. #### [PebbleKit iOS](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) How to use PebbleKit to communicate with a watchapp on iOS. #### [Sending and Receiving Data](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md) How to send and receive data between your watchapp and phone. #### [Sports API](https://developer.repebble.com/guides/communication/using-the-sports-api.md) How to use the PebbleKit Sports API to integrate your mobile sports app with Pebble. ## [7. Debugging](https://developer.repebble.com/guides/debugging.md) How to find and fix common compilation and runtime problems in apps. #### [Common Runtime Errors](https://developer.repebble.com/guides/debugging/common-runtime-errors.md) Examples of commonly encountered runtime problems that cannot be detected at compile time and can usually be fixed by logical thought and experimentation. #### [Common Syntax Errors](https://developer.repebble.com/guides/debugging/common-syntax-errors.md) Details of common problems encountered when writing C apps for Pebble, and how to resolve them. #### [Debugging Alloy Apps with xsbug](https://developer.repebble.com/guides/debugging/debugging-alloy-with-xsbug.md) Use the xsbug JavaScript debugger to set breakpoints and inspect your Alloy app while it runs on the watch. #### [Debugging with App Logs](https://developer.repebble.com/guides/debugging/debugging-with-app-logs.md) How to use the app logs to debug problems with an app, as well as tips on interpreting common run time errors. #### [Debugging with GDB](https://developer.repebble.com/guides/debugging/debugging-with-gdb.md) How to use GDB to debug a Pebble app in the emulator. ## [8. Design and Interaction](https://developer.repebble.com/guides/design-and-interaction.md) Information on creating the best app user experiences and layout designs. #### [Benefits of Design Guidelines](https://developer.repebble.com/guides/design-and-interaction/benefits.md) Learn the main concepts of design guidelines, why they are needed, and how they can help developers. #### [Core Experience Design](https://developer.repebble.com/guides/design-and-interaction/core-experience.md) How design guidelines shape the core Pebble app experience. #### [One Click Actions](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) Details about how to create One Click Action watchapps #### [Recommended Guidelines and Patterns](https://developer.repebble.com/guides/design-and-interaction/recommended.md) Pebble's recommended guidelines for creating awesome app experiences. #### [Round App Design](https://developer.repebble.com/guides/design-and-interaction/in-the-round.md) Tips and advice for designing apps that take advantage of the Pebble Time Round display #### [Example Implementations](https://developer.repebble.com/guides/design-and-interaction/implementation.md) Resources and code samples to implement common design and UI patterns. ## [9. Events and Services](https://developer.repebble.com/guides/events-and-services.md) How to get data from the onboard sensors and services including the accelerometer, compass, and microphone. #### [Accelerometer](https://developer.repebble.com/guides/events-and-services/accelerometer.md) How to use data and simple tap gestures from the onboard accelerometer. #### [Background Worker](https://developer.repebble.com/guides/events-and-services/background-worker.md) Using the Background Worker to do work in the background, such as activity tracking. #### [Backlight](https://developer.repebble.com/guides/events-and-services/light.md) How to trigger the backlight, force it on, and - on hardware with an RGB backlight - tint it. #### [Buttons](https://developer.repebble.com/guides/events-and-services/buttons.md) How to react to button presses in your app. #### [Compass](https://developer.repebble.com/guides/events-and-services/compass.md) How to use data from the Compass API to determine direction. #### [Dictation](https://developer.repebble.com/guides/events-and-services/dictation.md) How to use the Dictation API to get voice-to-text input in watchapps. #### [Event Services](https://developer.repebble.com/guides/events-and-services/events.md) How to use the various asynchronous event services to power app features. #### [Heart Rate Monitor](https://developer.repebble.com/guides/events-and-services/hrm.md) Information on using the HealthService API to obtain information from the Heart Rate Monitor. #### [Pebble Health](https://developer.repebble.com/guides/events-and-services/health.md) Information on using the HealthService API to incorporate multiple types of health data into your apps. #### [Persistent Storage](https://developer.repebble.com/guides/events-and-services/persistent-storage.md) Using persistent storage to improve your app's UX. #### [Speaker](https://developer.repebble.com/guides/events-and-services/speaker.md) How to play tones, melodies, polyphonic tracks, and PCM streams through the speaker. #### [Touch](https://developer.repebble.com/guides/events-and-services/touch.md) How to subscribe to touch events on platforms with a touchscreen. #### [Wakeups](https://developer.repebble.com/guides/events-and-services/wakeups.md) Using the Wakeup API to launch an app at some future time. ## [10. Graphics and Animations](https://developer.repebble.com/guides/graphics-and-animations.md) Information on using animations and drawing shapes, text, and images, as well as more advanced techniques. #### [Animations](https://developer.repebble.com/guides/graphics-and-animations/animations.md) How to use Animations and Timers to add life to your app. #### [Drawing Primitives, Images and Text](https://developer.repebble.com/guides/graphics-and-animations/drawing-primitives-images-and-text.md) How to draw primitive shapes, image, and text onto the Graphics Context. #### [Framebuffer Graphics](https://developer.repebble.com/guides/graphics-and-animations/framebuffer-graphics.md) How to perform advanced drawing using direct framebuffer access. #### [Vector Graphics](https://developer.repebble.com/guides/graphics-and-animations/vector-graphics.md) How to draw simple images using vector images, instead of bitmaps. ## [11. Pebble Packages](https://developer.repebble.com/guides/pebble-packages.md) How to create and use Pebble Packages #### [Creating Pebble Packages](https://developer.repebble.com/guides/pebble-packages/creating-packages.md) How to create Pebble Packages #### [Using Pebble Packages](https://developer.repebble.com/guides/pebble-packages/using-packages.md) How to use Pebble Packages ## [12. Pebble Timeline](https://developer.repebble.com/guides/pebble-timeline.md) How to use Pebble timeline to bring timely information to app users outside the app itself. #### [Creating Pins](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) How to create timeline pins with reminders, actions, and layouts. #### [Local Pins](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) How to insert pins into the timeline directly from PebbleKit JS, without a web server or timeline API keys. #### [Subscriptions and Tokens](https://developer.repebble.com/guides/pebble-timeline/timeline-js.md) The PebbleKit JS timeline subscription and token APIs, and what to use instead. ## [13. Plugins (Preview)](https://developer.repebble.com/guides/plugins.md) How a .pbw's JavaScript can provide data sources and actions to other apps through the Pebble mobile app, and how a watchapp reads them from PebbleKit JS. #### [Pebble Plugins Preview](https://developer.repebble.com/guides/plugins/preview.md) What plugins are, how to try the preview in the Pebble mobile app, how a watchapp reads plugin data from PebbleKit JS, and how to write a plugin. ## [14. Tools and Resources](https://developer.repebble.com/guides/tools-and-resources.md) Information on all the software tools available when writing Pebble apps, as well as other resources. #### [App Metadata](https://developer.repebble.com/guides/tools-and-resources/app-metadata.md) Details of the metadata that describes the app, such as its name, resources and capabilities. #### [Color Picker Tool](https://developer.repebble.com/guides/tools-and-resources/color-picker.md) Preview all the colors available on Pebble with the associated SDK contants and HTML codes. #### [Command Line Tool](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) How to use the Pebble command line tool to build, debug, and emulate apps. #### [Developer Connection](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) How to enable and use the Pebble Developer Connection to install and debug apps directly from a computer. #### [Hardware Information](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) Details of the the capabilities of the various Pebble hardware platforms. #### [Internationalization](https://developer.repebble.com/guides/tools-and-resources/internationalization.md) How to localize an app to multiple languages. #### [Testing with the Pebble Mobile App](https://developer.repebble.com/guides/tools-and-resources/testing-with-the-mobile-app.md) How to connect the Pebble mobile app to the emulator to test PebbleKit JS, configuration pages and timeline pins without a watch, and how to sideload a .pbw file. ## [15. User Interfaces](https://developer.repebble.com/guides/user-interfaces.md) How to build effective user interfaces. Includes information on events, persistent storage, background worker, wakeups and app configuration. #### [App Configuration](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) How to allow users to customize an app with a configuration page. #### [App Exit Reason](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md) Details on how to use the AppExitReason API #### [AppGlance C API](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) How to programatically update an app's app glance. #### [AppGlance in PebbleKit JS](https://developer.repebble.com/guides/user-interfaces/appglance-pebblekit-js.md) How to update an app's glance using PebbleKit JS. #### [Content Size](https://developer.repebble.com/guides/user-interfaces/content-size.md) Details on how to use the ContentSize API to adapt your watchface layout based on user text size preferences. #### [Layers](https://developer.repebble.com/guides/user-interfaces/layers.md) How to use standard Layer components to build an app's UI. #### [Round App UI](https://developer.repebble.com/guides/user-interfaces/round-app-ui.md) Details on how to use the Pebble SDK to create layouts specifically for round displays. #### [Unobstructed Area](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md) Details on how to use the UnobstructedArea API to adapt your watchface layout when the screen is partially obstructed by a system overlay. --- # Smartstraps Source: https://developer.repebble.com/guides/smartstraps/ > Note: Smartstraps are not supported on Pebble 2 Duo, Pebble Time 2, Pebble Round 2 or newer watches, and support will not be added. Smartstraps were accessories that connected to the smart accessory port on the back of Pebble Time, Pebble Time Steel, Pebble Time Round and Pebble 2. The port carried power and a one-wire serial bus, and the `Smartstrap` C API let an app exchange data with the strap. Pebble 2 Duo, Pebble Time 2, Pebble Round 2 and newer watches do not have the accessory port, and the `Smartstrap` API is not part of the current SDK reference. This page is kept as a historical record. The original guides on smartstrap hardware, the serial protocol and the C API remain in the [repository history](https://github.com/coredevices/sdk-docs/tree/c4ef713%5E/source/_guides/smartstraps). --- # Pebble Source: https://developer.repebble.com/docs/pebblekit-js/Pebble/ The Pebble namespace is where all of the Pebble specific methods and properties exist. This class contains methods belonging to PebbleKit JS and allows bi-directional communication with a C or JavaScript watchapp, as well as managing the user's timeline subscriptions, creating AppGlance slices and obtaining information about the currently connected watch. ### Methods Pebble.addEventListener(type, callback) Adds a listener for PebbleKit JS events, such as when an `AppMessage` is received or the configuration view is opened or closed. #### Event Type Options Possible values: - `ready` - The watchapp has been launched and the PebbleKit JS component is now ready to receive events. - `appmessage` - The watch sent an `AppMessage` to PebbleKit JS. The AppMessage `Dictionary` is contained in the payload property (i.e: `event.payload`). The payload consists of key-value pairs, where the keys are strings containing integers (e.g: "0"), or aliases for keys defined in package.json (e.g: "KEY\_EXAMPLE"). Values should be integers, strings or byte arrays (arrays of characters). - `showConfiguration` - The user has requested the app's configuration webview to be displayed. This can occur either upon the app's initial install or when the user taps 'Settings' in the 'My Pebble' view within the phone app. - `webviewclosed` - The configuration webview was closed by the user. If the webview had a response, it will be contained in the response property (i.e: `event.response`). This response can be used to feed back user preferences to the watchapp. #### Parameters String type The type of the event, from the list described above. EventCallback callback The developer defined [EventCallback](#EventCallback) to receive any events of the type specified that occur. Pebble.removeEventListener(type, callback) Remove an existing event listener previously registered with [Pebble.addEventListener()](#addEventListener). #### Parameters String type The type of the event listener to be removed. See [Pebble.addEventListener()](#addEventListener) for a list of available event types. Function callback The existing developer-defined function that was previously registered. Pebble.showSimpleNotificationOnPebble(title, body) Show a simple modal notification on the connected watch. #### Parameters String title The title of the notification String body The main content of the notification Pebble.sendAppMessage(data, onSuccess, onFailure) Send an AppMessage to the app running on the watch. Messages should be in the form of JSON objects containing key-value pairs. See Pebble.sendAppMessage() for valid key and value data types. `Pebble.sendAppMessage = function(data, onSuccess, onFailure) { };` #### Parameters Object data A JSON object containing key-value pairs to send to the watch. Values in arrays that are greater then 255 will be mod 255 before sending. AppMessageAckCallback onSuccess A developer-defined [AppMessageAckCallback](#AppMessageAckCallback) callback to run if the watch acknowledges (ACK) this message. AppMessageOnFailure onFailure A developer-defined [AppMessageNackCallback](#AppMessageNackCallback) callback to run if the watch does NOT acknowledge (NACK) this message. #### Returns The transaction id for this message Pebble.getTimelineToken(onSuccess, onFailure) The Pebble mobile app returns a token only for apps installed from the appstore. Sideloaded apps receive the failure callback. The timeline web API is no longer available, so the token cannot be used to push pins. Use [insertTimelinePin](#insertTimelinePin) instead. Get the user's timeline token for this app. This is a string and is unique per user per app. Note: In order for timeline tokens to be available, the app must be submitted to the Pebble appstore, but does not need to be public. Read more in the [timeline guides](https://developer.repebble.com/guides/pebble-timeline/timeline-js.md). #### Parameters TimelineTokenCallback onSuccess A developer-defined [TimelineTokenCallback](#TimelineTokenCallback) callback to handle a successful attempt to get the timeline token. Function onFailure A developer-defined callback to handle a failed attempt to get the timeline token. Pebble.timelineSubscribe(topic, onSuccess, onFailure) Not supported in the Pebble mobile app. The timeline web API is no longer available and the callbacks never fire. Use [insertTimelinePin](#insertTimelinePin) instead. Subscribe the user to a timeline topic for your app. This can be used to filter the different pins a user could receive according to their preferences, as well as maintain groups of users. #### Parameters String topic The desired topic to be subscribed to. Users will receive all pins pushed to this topic. Function onSuccess A developer-defined callback to handle a successful subscription attempt. Function onFailure A developer-defined callback to handle a failed subscription attempt. Pebble.timelineUnsubscribe(topic, onSuccess, onFailure) Not supported in the Pebble mobile app. The timeline web API is no longer available and the callbacks never fire. Use [insertTimelinePin](#insertTimelinePin) instead. Unsubscribe the user from a timeline topic for your app. Once unsubscribed, the user will no longer receive any pins pushed to this topic. #### Parameters String topic The desired topic to be unsubscribed from. Function onSuccess A developer-defined callback to handle a successful unsubscription attempt. Function onFailure A developer-defined callback to handle a failed unsubscription attempt. Pebble.timelineSubscriptions(onSuccess, onFailure) Not supported in the Pebble mobile app. The timeline web API is no longer available and the callbacks never fire. Use [insertTimelinePin](#insertTimelinePin) instead. Obtain a list of topics that the user is currently subscribed to. The length of the list should be checked to determine whether the user is subscribed to at least one topic. `Pebble.timelineSubscriptions(function(topics) { console.log(topics); }, function() { console.log('error'); } );` #### Parameters TimelineTopicsCallback onSuccess The developer-defined function to process the retuned list of topic strings. Function onFailure The developer-defined function to gracefully handle any errors in obtaining the user's subscriptions. Pebble.insertTimelinePin(pin) Insert a pin into the user's timeline. The pin is created by the newPebble app and synced to the watch, so no timeline token, API key orappstore listing is required and sideloaded apps can use it. The pin isassociated with your app, and its `id` only needs to be unique withinyour app - inserting a pin with an existing `id` updates that pin. Thenew Pebble app does not support the timeline web API, so this is how newapps should create pins. Read more in the[local pins guide](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md). #### Parameters Object pin The pin to insert, in the format described in[Creating Pins](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md). A JSONstring of the same object is also accepted. Pebble.deleteTimelinePin(id) Remove a pin previously added with[insertTimelinePin](#insertTimelinePin) from the user's timeline. #### Parameters String id The `id` of the pin to delete. Pebble.getActiveWatchInfo() Obtain an object containing information on the currently connected Pebble smartwatch. #### Returns A [WatchInfo](#WatchInfo) object detailing the currently connected Pebble watch. Pebble.getAccountToken() Returns a unique account token that is associated with the Pebble account of the current user. #### Returns A string that is guaranteed to be identical across devices if the user owns several Pebble or several mobile devices. From the developer's perspective, the account token of a user is identical across platforms and across all the developer's watchapps. If the user is not logged in, this function will return an empty string (''). Pebble.getWatchToken() Returns a a unique token that can be used to identify a Pebble device. #### Returns A string that is is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. Pebble.appGlanceReload(appGlanceSlices, onSuccess, onFailure) Not supported in the Pebble mobile app. The callbacks never fire. Use the [C AppGlance API](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) from the watchapp instead. Triggers a reload of the app glance which first clears any existing slices and then adds the provided slices. #### Parameters AppGlanceSlice appGlanceSlices [AppGlanceSlice](#AppGlanceSlice) JSON objects to add to the app glance. AppGlanceReloadSuccessCallback onSuccess The developer-defined callback which is called if the reload operation succeeds. AppGlanceReloadFailureCallback onFailure The developer-defined callback which is called if the reload operation fails. Pebble.openURL(url) When an app is marked as configurable, the PebbleKit JS component must implement `Pebble.openURL()` in the `showConfiguration` event handler. The Pebble mobile app will launch the supplied URL to allow the user to configure the watchapp or watchface. See the [App Configuration guide](https://developer.repebble.com/guides/user-interfaces/app-configuration-static.md). #### Parameters String url The URL of the static configuration page. Pebble.enumeratePlugins() Returns the installed plugins. Each entry has `uuid`, `name`, `sources` and `actions`, copied from the plugin's manifest. Returns an empty array when the call fails. **Preview.** Does nothing useful until the plugins preview is turned on in the Pebble mobile app. The API will change. See the [Plugins (Preview) guide](https://developer.repebble.com/guides/plugins.md). #### Returns An array of plugin descriptions. Pebble.subscribeToSource(config) Subscribes to one item of one category and delivers every update to `config.onData` as an envelope with `pluginUuid`, `validUntilMs` and `instances`. Throws when `category` or `item` is missing. Failures the Pebble mobile app detects, such as no plugin for the item or a missing permission, are reported to `config.onError` as `{ code, message }`. **Preview.** Does nothing useful until the plugins preview is turned on in the Pebble mobile app. The API will change. See the [Plugins (Preview) guide](https://developer.repebble.com/guides/plugins.md). #### Parameters Object config An object with `category` and `item` (required), and optional `properties` (array of property names), `plugin` (UUID of a preferred plugin), `iconPixelSize` (`{ w, h }`, needed for `icon` and `image` shapes), `onData` and `onError` callbacks. #### Returns A subscription object with an `unsubscribe()` method. The subscription also ends when the app's JavaScript stops. Pebble.invokeAction(config) Invokes one action on one plugin. The returned Promise always resolves: with `{ ok: true, text, refreshed }` on success, or `{ ok: false, code, message }`, with text, refreshed and message omitted when the plugin did not set them. Throws when `plugin` or `action` is missing. Actions never fall back to another plugin. **Preview.** Does nothing useful until the plugins preview is turned on in the Pebble mobile app. The API will change. See the [Plugins (Preview) guide](https://developer.repebble.com/guides/plugins.md). #### Parameters Object config An object with `plugin` (the plugin's UUID) and `action` (required), `args` (an object matching the action's parameters schema, default `{}`) and `timeoutMs` (1 to 60000, default 15000). #### Returns A Promise that resolves with the action result. Pebble.sendConfigMessage(message) Pushes a message to the app's own settings page while that page is open. The page receives it as a `message` event with `e.target` set to `"pkjs"` and `e.data` set to the message. There is no reply, and nothing happens when no page is open. **Preview.** Does nothing useful until the plugins preview is turned on in the Pebble mobile app. The API will change. See the [Plugins (Preview) guide](https://developer.repebble.com/guides/plugins.md). #### Parameters Object message Any JSON value to deliver to the page. ### Typedefs Pebble.TimelineTopicsCallback(List) Called when the user's list of subscriptions is available for processing by the developer. #### Parameters List of topic strings that the user is subscribed to Pebble.AppGlanceReloadFailureCallback(AppGlanceSlices) Called when AppGlanceReload has failed. #### Parameters AppGlanceSlice AppGlanceSlices An [AppGlanceSlice](#AppGlanceSlice) object containing the app glance slices. Pebble.AppMessageAckCallback(data) Called when an AppMessage is acknowledged by the watch. #### Parameters Object data An object containing the callback data. This contains the `transactionId` which is the transaction ID of the message. Pebble.AppMessageNackCallback(data, error) Called when an AppMessage is not acknowledged by the watch. #### Parameters Object data An object containing the callback data. This contains the `transactionId` which is the transaction ID of the message String error The error message Pebble.EventCallback(event) Called when an event of any type previously registered occurs. The parameters are different depending on the type of event, shown in brackets for each parameter listed here. #### Parameters Object event An object containing the event information, including: - `type` - The type of event fired, from the list in the description of [Pebble.addEventListener()](#addEventListener). - `payload` - The dictionary sent over `AppMessage` consisting of key-value pairs. - `response` - The contents of the URL navigated to when the configuration page was closed, after the anchor. This may be encoded, which will require use of decodeURIComponent() before reading as an object. Pebble.TimelineTokenCallback(token) Called when the user's timeline token is available. #### Parameters String token The user's token. Pebble.AppGlanceReloadSuccessCallback(AppGlanceSlices) Called when AppGlanceReload is successful. #### Parameters AppGlanceSlice AppGlanceSlices An [AppGlanceSlice](#AppGlanceSlice) object containing the app glance slices. WatchInfo Provides information about the connected Pebble smartwatch. #### Properties platform The hardware platform, such as `basalt` or `emery`. model The watch model, such as `pebble_black` language The user's currently selected language on this watch. firmware An object containing information about the watch's firmware version, including: - `major` - The major version - `minor` - The minor version - `patch` - The patch version - `suffix` - Any additional version information, such as `beta3` AppGlanceSlice The structure of an app glance. #### Properties expirationTime Optional ISO date-time string of when the entry should expire and no longer be shown in the app glance. layout An object containing: - `icon` - URI string of the icon to display in the app glance, e.g. system://images/ALARM\_CLOCK. - `subtitleTemplateString` - Template string that will be displayed in the subtitle of the app glance. --- # PebbleKit JavaScript Documentation Source: https://developer.repebble.com/docs/pebblekit-js/ PebbleKit JS is a JavaScript component of the Pebble SDK which runs within the Pebble mobile application. It provides access to GPS, storage and Internet connectivity to applications running on Pebble smartwatches. Want to create apps or faces for Pebble written entirely in JS? For that, use [Alloy](https://developer.repebble.com/guides/alloy.md). The PebbleKit JS API is provided via the `Pebble` namespace: * * * #### [Pebble](https://developer.repebble.com/docs/pebblekit-js/Pebble.md) * * * PebbleKit JS also provides access to the following standard JS functionality: - [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) - [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) - [Geolocation](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) - [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/LocalStorage) --- # AccelerometerService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService/ Using the Pebble accelerometer The AccelerometerService enables the Pebble accelerometer to detect taps, perform measures at a given frequency, and transmit samples in batches to save CPU time and processing. For available code samples, see the [feature-accel-discs](https://github.com/pebble-examples/feature-accel-discs/) example app. ## Function Documentation int accel\_service\_peek([AccelData](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelData) \* data) Peek at the last recorded reading. ##### Note Cannot be used when subscribed to accelerometer data events. #### Parameters data (out) a pointer to a pre-allocated [AccelData](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelData) item #### Returns -1 if the accel is not running -2 if subscribed to accelerometer events. int accel\_service\_set\_sampling\_rate([AccelSamplingRate](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelSamplingRate) rate) Change the accelerometer sampling rate. #### Parameters rate The sampling rate in Hz (10Hz, 25Hz, 50Hz, and 100Hz possible) int accel\_service\_set\_samples\_per\_update([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_samples) Change the number of samples buffered between each accelerometer data event. #### Parameters num\_samples the number of samples to buffer, between 0 and 25. void accel\_data\_service\_subscribe([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) samples\_per\_update, [AccelDataHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelDataHandler) handler) Subscribe to the accelerometer data event service. Once subscribed, the handler gets called every time there are new accelerometer samples available. ##### Note Cannot use [accel\_service\_peek()](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_service_peek) when subscribed to accelerometer data events. #### Parameters handler A callback to be executed on accelerometer data events samples\_per\_update the number of samples to buffer, between 0 and 25. void accel\_data\_service\_unsubscribe(void) Unsubscribe from the accelerometer data event service. Once unsubscribed, the previously registered handler will no longer be called. void accel\_tap\_service\_subscribe([AccelTapHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelTapHandler) handler) Subscribe to the accelerometer tap event service. Once subscribed, the handler gets called on every tap event emitted by the accelerometer. #### Parameters handler A callback to be executed on tap event void accel\_tap\_service\_unsubscribe(void) Unsubscribe from the accelerometer tap event service. Once unsubscribed, the previously registered handler will no longer be called. void accel\_raw\_data\_service\_subscribe([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) samples\_per\_update, [AccelRawDataHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelRawDataHandler) handler) Subscribe to the accelerometer raw data event service. Once subscribed, the handler gets called every time there are new accelerometer samples available. ##### Note Cannot use [accel\_service\_peek()](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_service_peek) when subscribed to accelerometer data events. #### Parameters handler A callback to be executed on accelerometer data events samples\_per\_update the number of samples to buffer, between 0 and 25. ## Data Structure Documentation struct AccelData A single accelerometer sample for all three axes including timestamp and vibration rumble status. #### Data Fields int16\_t x acceleration along the x axis int16\_t y acceleration along the y axis int16\_t z acceleration along the z axis bool did\_vibrate true if the watch vibrated when this sample was collected uint64\_t timestamp timestamp, in milliseconds struct AccelRawData A single accelerometer sample for all three axes. #### Data Fields int16\_t x acceleration along the x axis int16\_t y acceleration along the y axis int16\_t z acceleration along the z axis ## Enum Documentation enum AccelAxisType Enumerated values defining the three accelerometer axes. #### Enumerators ACCEL\_AXIS\_X Accelerometer's X axis. The positive direction along the X axis goes toward the right of the watch. ACCEL\_AXIS\_Y Accelerometer's Y axis. The positive direction along the Y axis goes toward the top of the watch. ACCEL\_AXIS\_Z Accelerometer's Z axis. The positive direction along the Z axis goes vertically out of the watchface. enum AccelSamplingRate Valid accelerometer sampling rates, in Hz. #### Enumerators ACCEL\_SAMPLING\_10HZ 10 HZ sampling rate ACCEL\_SAMPLING\_25HZ 25 HZ sampling rate [Default] ACCEL\_SAMPLING\_50HZ 50 HZ sampling rate ACCEL\_SAMPLING\_100HZ 100 HZ sampling rate ## Typedef Documentation typedef void(\* AccelDataHandler)(AccelData \*data, uint32\_t num\_samples) Callback type for accelerometer data events. #### Parameters data Pointer to the collected accelerometer samples. num\_samples the number of samples stored in data. typedef void(\* AccelRawDataHandler)(AccelRawData \*data, uint32\_t num\_samples, uint64\_t timestamp) Callback type for accelerometer raw data events. #### Parameters data Pointer to the collected accelerometer samples. num\_samples the number of samples stored in data. timestamp the timestamp, in ms, of the first sample. typedef void(\* AccelTapHandler)(AccelAxisType axis, int32\_t direction) Callback type for accelerometer tap events. #### Parameters axis the axis on which a tap was registered (x, y, or z) direction the direction (-1 or +1) of the tap --- # ActionBarLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer/ Vertical, bar-shaped control widget on the right edge of the window ![](https://developer.repebble.com/assets/images/docs/c/aplite/action_bar_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/action_bar_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/action_bar_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/action_bar_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/action_bar_layer.png) ActionBarLayer is a Layer that displays a bar on the right edge of the window. The bar can contain up to 3 icons, each corresponding with one of the buttons on the right side of the watch. The purpose of each icon is to provide a hint (feed-forward) to what action a click on the respective button will cause. The action bar is useful when there are a few (up to 3) main actions that are desirable to be able to take quickly, literally with one press of a button. ### More actions If there are more than 3 actions the user might want to take: - Try assigning the top and bottom icons of the action bar to the two most immediate actions and use the middle icon to push a Window with a MenuLayer with less immediate actions. - Secondary actions that are not vital, can be "hidden" under a long click. Try to group similar actions to one button. For example, in a Music app, a single click on the top button is tied to the action to jump to the previous track. Holding that same button means seek backwards. ### Directionality mapping When the top and bottom buttons are used to control navigating through a (potentially virtual, non-visible) list of items, follow this guideline: - Tie the top button to the action that goes to the _previous_ item in the list, for example "jump to previous track" in a Music app. - Tie the bottom button to the action that goes to the _next_ item in the list, for example "jump to next track" in a Music app. ### Geometry - The action bar's width varies per platform. 30px on most displays, 34px on Emery and Gabbro, and 40px on Chalk. Use the [ACTION\_BAR\_WIDTH](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ACTION_BAR_WIDTH) define. - Icons should not be wider than 28 pixels, or taller than 18 pixels. It is recommended to use a size of around 15 x 15 pixels for the "visual core" of the icon, and extending or contracting where needed. The code example below shows how to do the initial setup of the action bar in a window's `.load` handler. Configuring the button actions is similar to the process when using [window\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider). See [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) for more information. ``` ActionBarLayer \*action\_bar;// The implementation of my\_next\_click\_handler and my\_previous\_click\_handler// is omitted for the sake of brevity. See the Clicks reference docs.void click\_config\_provider(void \*context) {window\_single\_click\_subscribe(BUTTON\_ID\_DOWN, (ClickHandler) my\_next\_click\_handler);window\_single\_click\_subscribe(BUTTON\_ID\_UP, (ClickHandler) my\_previous\_click\_handler);}void window\_load(Window \*window) {...// Initialize the action bar:action\_bar = action\_bar\_layer\_create();// Associate the action bar with the window:action\_bar\_layer\_add\_to\_window(action\_bar, window);// Set the click config provider:action\_bar\_layer\_set\_click\_config\_provider(action\_bar,click\_config\_provider);// Set the icons:// The loading of the icons is omitted for brevity... See gbitmap\_create\_with\_resource()action\_bar\_layer\_set\_icon\_animated(action\_bar, BUTTON\_ID\_UP, my\_icon\_previous, true);action\_bar\_layer\_set\_icon\_animated(action\_bar, BUTTON\_ID\_DOWN, my\_icon\_next, true);} ``` ## Function Documentation ActionBarLayer \* action\_bar\_layer\_create(void) Creates a new ActionBarLayer on the heap and initalizes it with the default values. - Background color: [GColorBlack](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlack) - No click configuration provider (`NULL`) - No icons - Not added to / associated with any window, thus not catching any button input yet. #### Returns A pointer to the ActionBarLayer. `NULL` if the ActionBarLayer could not be created void action\_bar\_layer\_destroy(ActionBarLayer \* action\_bar\_layer) Destroys a ActionBarLayer previously created by action\_bar\_layer\_create. Layer \* action\_bar\_layer\_get\_layer(ActionBarLayer \* action\_bar\_layer) Gets the "root" Layer of the action bar layer, which is the parent for the sub- layers used for its implementation. #### Parameters action\_bar\_layer Pointer to the ActionBarLayer for which to get the "root" Layer #### Returns The "root" Layer of the action bar layer. void action\_bar\_layer\_set\_context(ActionBarLayer \* action\_bar, void \* context) Sets the context parameter, which will be passed in to [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) callbacks and the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) callback of the action bar. ##### Note By default, a pointer to the action bar itself is passed in, if the context has not been set or if it has been set to `NULL`. #### Parameters action\_bar The action bar for which to assign the new context context The new context #### See Also [action\_bar\_layer\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_click_config_provider) [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) void action\_bar\_layer\_set\_click\_config\_provider(ActionBarLayer \* action\_bar, [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) click\_config\_provider) Sets the click configuration provider callback of the action bar. In this callback your application can associate handlers to the different types of click events for each of the buttons, see [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md). ##### Notes If the action bar had already been added to a window and the window is currently on-screen, the click configuration provider will be called before this function returns. Otherwise, it will be called by the system when the window becomes on-screen. The `.raw` handlers cannot be used without breaking the automatic highlighting of the segment of the action bar that for which a button is #### Parameters action\_bar The action bar for which to assign a new click configuration provider click\_config\_provider The new click configuration provider #### See Also [action\_bar\_layer\_set\_icon()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon) void action\_bar\_layer\_set\_icon(ActionBarLayer \* action\_bar, [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, const GBitmap \* icon) Sets an action bar icon onto one of the 3 slots as identified by `button_id`. Only BUTTON\_ID\_UP, BUTTON\_ID\_SELECT and BUTTON\_ID\_DOWN can be used. The transition will not be animated. Whenever an icon is set, the click configuration provider will be called, to give the application the opportunity to reconfigure the button interaction. #### Parameters action\_bar The action bar for which to set the new icon button\_id The identifier of the button for which to set the icon icon Pointer to the GBitmap icon #### See Also [action\_bar\_layer\_set\_icon\_animated()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_animated) [action\_bar\_layer\_set\_icon\_press\_animation()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_press_animation) [action\_bar\_layer\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_click_config_provider) void action\_bar\_layer\_clear\_icon(ActionBarLayer \* action\_bar, [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id) Convenience function to clear out an existing icon. All it does is call `action_bar_layer_set_icon(action_bar, button_id, NULL)` #### Parameters action\_bar The action bar for which to clear an icon button\_id The identifier of the button for which to clear the icon #### See Also [action\_bar\_layer\_set\_icon()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon) void action\_bar\_layer\_add\_to\_window(ActionBarLayer \* action\_bar, struct Window \* window) Adds the action bar's layer on top of the window's root layer. It also adjusts the layout of the action bar to match the geometry of the window it gets added to. Lastly, it calls [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) on the window to set it up to work with the internal callback and raw click handlers of the action bar, to enable the highlighting of the section of the action bar when the user presses a button. ##### Notes After this call, do not use [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) with the window that the action bar has been added to (this would de-associate the action bar's click config provider and context). Instead use [action\_bar\_layer\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_click_config_provider) and [action\_bar\_layer\_set\_context()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_context) to register the click configuration provider to configure the buttons actions. It is advised to call this is in the window's `.load` or `.appear` handler. Make sure to call [action\_bar\_layer\_remove\_from\_window()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_remove_from_window) in the window's `.unload` or `.disappear` handler. Adding additional layers to the window's root layer after this calll can occlude the action bar. #### Parameters action\_bar The action bar to associate with the window window The window with which the action bar is to be associated void action\_bar\_layer\_remove\_from\_window(ActionBarLayer \* action\_bar) Removes the action bar from the window and unconfigures the window's click configuration provider. `NULL` is set as the window's new click config provider and also as its callback context. If it has not been added to a window before, this function is a no-op. #### Parameters action\_bar The action bar to de-associate from its current window void action\_bar\_layer\_set\_background\_color(ActionBarLayer \* action\_bar, GColor background\_color) Sets the background color of the action bar. Defaults to [GColorBlack](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlack). The action bar's layer is automatically marked dirty. #### Parameters action\_bar The action bar of which to set the background color background\_color The new background color void action\_bar\_layer\_set\_icon\_animated(ActionBarLayer \* action\_bar, [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, const GBitmap \* icon, bool animated) Sets an action bar icon onto one of the 3 slots as identified by `button_id`. Only BUTTON\_ID\_UP, BUTTON\_ID\_SELECT and BUTTON\_ID\_DOWN can be used. Optionally, if `animated` is `true`, the transition will be animated. Whenever an icon is set, the click configuration provider will be called, to give the application the opportunity to reconfigure the button interaction. #### Parameters action\_bar The action bar for which to set the new icon button\_id The identifier of the button for which to set the icon icon Pointer to the GBitmap icon animated True = animate the transition, False = do not animate the transition #### See Also [action\_bar\_layer\_set\_icon()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon) [action\_bar\_layer\_set\_icon\_press\_animation()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_press_animation) [action\_bar\_layer\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_click_config_provider) void action\_bar\_layer\_set\_icon\_press\_animation(ActionBarLayer \* action\_bar, [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, ActionBarLayerIconPressAnimation animation) Sets the animation to use while a button is pressed on an ActionBarLayer. By default we use ActionBarLayerIconPressAnimationMoveLeft. #### Parameters action\_bar The action bar for which to set the press animation button\_id The button for which to set the press animation animation The animation to use. #### See Also [action\_bar\_layer\_set\_icon\_animated()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_animated) [action\_bar\_layer\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_click_config_provider) ## Enum Documentation enum ActionBarLayerIconPressAnimation #### Enumerators ActionBarLayerIconPressAnimationNoneActionBarLayerIconPressAnimationMoveLeftActionBarLayerIconPressAnimationMoveUpActionBarLayerIconPressAnimationMoveRightActionBarLayerIconPressAnimationMoveDown ## Typedef Documentation typedef struct ActionBarLayer ActionBarLayer ## Macro Definition Documentation #define ACTION\_BAR\_WIDTH [\_ACTION\_BAR\_WIDTH](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#_ACTION_BAR_WIDTH)(PBL\_PLATFORM\_TYPE\_CURRENT) The width of the action bar in pixels. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define \_ACTION\_BAR\_WIDTH ( plat) The width of the action bar in pixels, for all platforms. #define \_ACTION\_BAR\_WIDTH ( plat) The width of the action bar in pixels, for all platforms. #define \_ACTION\_BAR\_WIDTH ( plat) The width of the action bar in pixels, for all platforms. #define NUM\_ACTION\_BAR\_ITEMS 3 The maximum number of action bar items. --- # ActionMenu Source: https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu/ ## Function Documentation char \* action\_menu\_item\_get\_label(const ActionMenuItem \* item) Getter for the label of a given ActionMenuItem. #### Parameters item the ActionMenuItem of interest #### Returns a pointer to the string label. NULL if invalid. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void \* action\_menu\_item\_get\_action\_data(const ActionMenuItem \* item) Getter for the action\_data pointer of a given ActionMenuitem. #### Parameters item the ActionMenuItem of interest #### Returns a pointer to the data. NULL if invalid. #### See Also [action\_menu\_level\_add\_action](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_add_action) void \* action\_menu\_item\_get\_action\_data(const ActionMenuItem \* item) Getter for the action\_data pointer of a given ActionMenuitem. #### Parameters item the ActionMenuItem of interest #### Returns a pointer to the data. NULL if invalid. #### See Also [action\_menu\_level\_add\_action](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_add_action) void \* action\_menu\_item\_get\_action\_data(const ActionMenuItem \* item) Getter for the action\_data pointer of a given ActionMenuItem. #### Parameters item the ActionMenuItem of interest #### Returns a pointer to the data. NULL if invalid. #### See Also [action\_menu\_level\_add\_action](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_add_action) ActionMenuLevel \* action\_menu\_level\_create([uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) max\_items) Create a new action menu level with storage allocated for a given number of items. ##### Notes levels are freed alongside the whole hierarchy so no destroy API is provided. by default, levels are using ActionMenuLevelDisplayModeWide. Use [action\_menu\_level\_set\_display\_mode](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_set_display_mode) to change it. #### Parameters max\_items the max number of items that will be displayed at that level #### See Also [action\_menu\_hierarchy\_destroy](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_hierarchy_destroy) void action\_menu\_level\_set\_display\_mode(ActionMenuLevel \* level, [ActionMenuLevelDisplayMode](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuLevelDisplayMode) display\_mode) Set the action menu display mode. #### Parameters level The ActionMenuLevel whose display mode you want to change display\_mode The display mode for the action menu (3 vs. 1 item per row) ActionMenuItem \* action\_menu\_level\_add\_action(ActionMenuLevel \* level, const char \* label, [ActionMenuPerformActionCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuPerformActionCb) cb, void \* action\_data) Add an action to an ActionLevel. #### Parameters level the level to add the action to label the text to display for the action in the menu cb the callback that will be triggered when this action is actuated action\_data data to pass to the callback for this action #### Returns a reference to the new ActionMenuItem on success, NULL if the level is full ActionMenuItem \* action\_menu\_level\_add\_child(ActionMenuLevel \* level, ActionMenuLevel \* child, const char \* label) Add a child to this ActionMenuLevel. #### Parameters level the parent level child the child level label the text to display in the action menu for this level #### Returns a reference to the new ActionMenuItem on success, NULL if the level is full void action\_menu\_hierarchy\_destroy(const ActionMenuLevel \* root, [ActionMenuEachItemCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuEachItemCb) each\_cb, void \* context) Destroy a hierarchy of ActionMenuLevels. ##### Notes Typical implementations will cleanup memory allocated for the item label/data associated with each item in the callback Hierarchy is traversed in post-order. In other words, all children items are freed before their parent is freed. #### Parameters root the root level in the hierarchy each\_cb a callback to call on every ActionMenuItem in every level context a context pointer to pass to each\_cb on invocation void \* action\_menu\_get\_context(ActionMenu \* action\_menu) Get the context pointer this ActionMenu was created with. #### Parameters action\_menu A pointer to an ActionMenu #### Returns the context pointer initially provided in the [ActionMenuConfig](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuConfig). NULL if none exists. ActionMenuLevel \* action\_menu\_get\_root\_level(ActionMenu \* action\_menu) Get the root level of an ActionMenu. #### Parameters action\_menu the ActionMenu you want to know about #### Returns a pointer to the root ActionMenuLevel for the given ActionMenu, NULL if invalid ActionMenu \* action\_menu\_open([ActionMenuConfig](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuConfig) \* config) Open a new ActionMenu. The ActionMenu acts much like a window. It fills the whole screen and handles clicks. #### Parameters config the configuration info for this new ActionMenu #### Returns the new ActionMenu void action\_menu\_freeze(ActionMenu \* action\_menu) Freeze the ActionMenu. The ActionMenu will no longer respond to user input. ##### Note this API should be used when waiting for asynchronous operation. #### Parameters action\_menu the ActionMenu void action\_menu\_unfreeze(ActionMenu \* action\_menu) Unfreeze the ActionMenu previously frozen with [action\_menu\_freeze](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_freeze). #### Parameters action\_menu the ActionMenu to unfreeze void action\_menu\_set\_result\_window(ActionMenu \* action\_menu, Window \* result\_window) Set the result window for an ActionMenu. The result window will be shown when the ActionMenu closes. ##### Note repeated call will result in only the last call to be applied, i.e. only one result window is ever set #### Parameters action\_menu the ActionMenu result\_window the window to insert, pass NULL to remove the current result window void action\_menu\_close(ActionMenu \* action\_menu, bool animated) Close the ActionMenu, whether it is frozen or not. ##### Note this API can be used on a frozen ActionMenu once the data required to build the result window has been received and the result window has been set #### Parameters action\_menu the ActionMenu to close animated whether or not show a close animation ## Data Structure Documentation struct ActionMenuConfig Configuration struct for the ActionMenu. #### Data Fields const ActionMenuLevel \* root\_level the root level of the ActionMenu void \* context a context pointer which will be accessbile when actions are performed struct [ActionMenuConfig](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuConfig) colors[ActionMenuDidCloseCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuDidCloseCb) will\_close Called immediately before the ActionMenu closes. [ActionMenuDidCloseCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuDidCloseCb) did\_close a callback used to cleanup memory after the menu has closed ActionMenuAlign align ## Enum Documentation enum ActionMenuAlign #### Enumerators ActionMenuAlignTopActionMenuAlignCenter enum ActionMenuLevelDisplayMode enum value that controls whether menu items are displayed in a grid (similarly to the emoji replies) or in a single column (reminiscent of [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md)) #### Enumerators ActionMenuLevelDisplayModeWide Each item gets its own row. ActionMenuLevelDisplayModeThin Grid view: multiple items per row. ## Typedef Documentation typedef struct ActionMenuItem ActionMenuItem typedef struct ActionMenuLevel ActionMenuLevel typedef struct ActionMenu ActionMenu - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) typedef void(\* ActionMenuDidCloseCb)(ActionMenu \*menu, const ActionMenuItem \*performed\_action, void \*context) Callback executed after the ActionMenu has closed, so memory may be freed. #### Parameters root\_level the root level passed to the ActionMenu performed\_action the ActionMenuItem for the action that was performed, NULL if the ActionMenu is closing without an action being selected by the user context the context passed to the ActionMenu typedef void(\* ActionMenuDidCloseCb)(ActionMenu \*menu, const ActionMenuItem \*performed\_action, void \*context) Callback executed after the ActionMenu has closed, so memory may be freed. #### Parameters root\_level the root level passed to the ActionMenu performed\_action the ActionMenuItem for the action that was performed, NULL if the ActionMenu is closing without an action being selected by the user context the context passed to the ActionMenu typedef void(\* ActionMenuDidCloseCb)(ActionMenu \*menu, const ActionMenuItem \*performed\_action, void \*context) Callback executed after the ActionMenu has closed, so memory may be freed. #### Parameters menu the ActionMenu performed\_action the ActionMenuItem for the action that was performed, NULL if the ActionMenu is closing without an action being selected by the user context the context passed to the ActionMenu typedef void(\* ActionMenuPerformActionCb)(ActionMenu \*action\_menu, const ActionMenuItem \*action, void \*context) Callback executed when a given action is selected. ##### Note the action menu is closed immediately after an action is performed, unless it is frozen in the [ActionMenuPerformActionCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuPerformActionCb) #### Parameters action\_menu the action menu currently on screen action the action that was triggered context the context passed to the action menu - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) typedef void(\* ActionMenuEachItemCb)(const ActionMenuItem \*item, void \*context) Callback invoked for each item in an action menu hierarchy. #### Parameters item the current action menu item a caller-provided context callback typedef void(\* ActionMenuEachItemCb)(const ActionMenuItem \*item, void \*context) Callback invoked for each item in an action menu hierarchy. #### Parameters item the current action menu item a caller-provided context callback typedef void(\* ActionMenuEachItemCb)(const ActionMenuItem \*item, void \*context) Callback invoked for each item in an action menu hierarchy. #### Parameters item the current action menu item context a caller-provided callback context --- # AlarmService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService/ The APIs on this page will only work with SDK 4.9+. ## Function Documentation bool alarm\_service\_peek\_next([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* timestamp\_out) Peek at the next scheduled, enabled alarm. #### Parameters timestamp\_out Must point to a valid `time_t`. If an enabled alarm exists, it is set to the UTC time at which that alarm fires. #### Returns true if at least one enabled alarm is scheduled, false otherwise. --- # Alloy Source: https://developer.repebble.com/docs/c/Foundation/Alloy/ The APIs on this page will only work with SDK 4.9+. ## Function Documentation void moddable\_createMachine([ModdableCreationRecord](https://developer.repebble.com/docs/c/Foundation/Alloy.md#ModdableCreationRecord) \* creation) Create and start a Moddable XS virtual machine for an Alloy app. #### Parameters creation Configuration record, or NULL for default settings. #### See Also [ModdableCreationRecord](https://developer.repebble.com/docs/c/Foundation/Alloy.md#ModdableCreationRecord) ## Data Structure Documentation struct ModdableCreationRecord Configuration record for creating a Moddable XS virtual machine. Used with [moddable\_createMachine()](https://developer.repebble.com/docs/c/Foundation/Alloy.md#moddable_createMachine) to customize the JS runtime. Set recordSize to sizeof(ModdableCreationRecord) for version compatibility. #### Data Fields [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) recordSize Size of this struct in bytes (for versioning) [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) stack Stack size in bytes (0 for default) [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) slot Slot heap size in bytes (0 for default) [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) chunk Chunk heap size in bytes (0 for default) [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) flags Combination of kModdableCreationFlag\* values. void \* fxBuildFFI Optional pointer to an fxBuildFFI function for custom native bindings. ## Macro Definition Documentation #define kModdableCreationFlagLogInstrumentation (1 \<\< 0) Flag to enable XS instrumentation logging over Bluetooth. When set, the Moddable XS engine will log instrumentation data (e.g. memory usage, slot/chunk/stack statistics) via app\_log. Logging is only active when a Bluetooth log listener is connected; otherwise this flag has no effect. #### See Also [ModdableCreationRecord](https://developer.repebble.com/docs/c/Foundation/Alloy.md#ModdableCreationRecord) #define kModdableCreationFlagDebug (1 \<\< 1) Flag to enable XS debugging using xsbug. Should be used in combination with pebble build -debug. #### See Also [ModdableCreationRecord](https://developer.repebble.com/docs/c/Foundation/Alloy.md#ModdableCreationRecord) --- # Animation Source: https://developer.repebble.com/docs/c/User_Interface/Animation/ Abstract framework to create arbitrary animations The Animation framework provides your Pebble app with an base layer to create arbitrary animations. The simplest way to work with animations is to use the layer frame [PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md), which enables you to move a Layer around on the screen. Using [animation\_set\_implementation()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_implementation), you can implement a custom animation. Refer to the [User Interface Layers chapter in the Pebble Developer Guides](https://developer.repebble.com/guides/user-interfaces/layers.md) (chapter "Animation") for a conceptual overview of the animation framework and on how to write custom animations. ## Modules #### [PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md) A ProperyAnimation animates the value of a "property" of a "subject" over time. ## Function Documentation Animation \* animation\_create(void) Creates a new Animation on the heap and initalizes it with the default values. - Duration: 250ms, - Curve: AnimationCurveEaseInOut (ease-in-out), - Delay: 0ms, - Handlers: `{NULL, NULL}` (none), - Context: `NULL` (none), - Implementation: `NULL` (no implementation), - Scheduled: no #### Returns A pointer to the animation. `NULL` if the animation could not be created bool animation\_destroy(Animation \* animation) Destroys an Animation previously created by animation\_create. #### Returns true if successful, false on failure Animation \* animation\_clone(Animation \* from) Animation \* animation\_sequence\_create(Animation \* animation\_a, Animation \* animation\_b, Animation \* animation\_c, ...) Create a new sequence animation from a list of 2 or more other animations. The returned animation owns the animations that were provided as arguments and no further write operations on those handles are allowed. The variable length argument list must be terminated with a NULL ptr. ##### Note the maximum number of animations that can be supplied to this method is 20 #### Parameters animation\_a the first required component animation animation\_b the second required component animation animation\_c either the third component, or NULL if only adding 2 components #### Returns The newly created sequence animation Animation \* animation\_sequence\_create\_from\_array(Animation \*\* animation\_array, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) array\_len) An alternate form of [animation\_sequence\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_sequence_create) that accepts an array of other animations. ##### Note the maximum number of elements allowed in animation\_array is 256 #### Parameters animation\_array an array of component animations to include array\_len the number of elements in the animation\_array #### Returns The newly created sequence animation Animation \* animation\_spawn\_create(Animation \* animation\_a, Animation \* animation\_b, Animation \* animation\_c, ...) Create a new spawn animation from a list of 2 or more other animations. The returned animation owns the animations that were provided as arguments and no further write operations on those handles are allowed. The variable length argument list must be terminated with a NULL ptr. ##### Note the maximum number of animations that can be supplied to this method is 20 #### Parameters animation\_a the first required component animation animation\_b the second required component animation animation\_c either the third component, or NULL if only adding 2 components #### Returns The newly created spawn animation or NULL on failure Animation \* animation\_spawn\_create\_from\_array(Animation \*\* animation\_array, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) array\_len) An alternate form of [animation\_spawn\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_spawn_create) that accepts an array of other animations. ##### Note the maximum number of elements allowed in animation\_array is 256 #### Parameters animation\_array an array of component animations to include array\_len the number of elements in the animation\_array #### Returns The newly created spawn animation or NULL on failure bool animation\_set\_elapsed(Animation \* animation, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) elapsed\_ms) Seek to a specific location in the animation. Only forward seeking is allowed. Returns true if successful, false if the passed in seek location is invalid. #### Parameters animation the animation for which to set the elapsed. elapsed\_ms the new elapsed time in milliseconds #### Returns true if successful, false if the requested elapsed is invalid. bool animation\_get\_elapsed(Animation \* animation, int32\_t \* elapsed\_ms) Get the current location in the animation. ##### Note The animation must be scheduled to get the elapsed time. If it is not schedule, this method will return false. #### Parameters animation The animation for which to fetch the elapsed. elapsed\_ms (out) pointer to variable that will contain the elapsed time in milliseconds #### Returns true if successful, false on failure bool animation\_set\_reverse(Animation \* animation, bool reverse) Set an animation to run in reverse (or forward) ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation the animation to operate on reverse set to true to run in reverse, false to run forward #### Returns true if successful, false on failure bool animation\_get\_reverse(Animation \* animation) Get the reverse setting of an animation. #### Parameters animation The animation for which to get the setting #### Returns the reverse setting bool animation\_set\_play\_count(Animation \* animation, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) play\_count) Set an animation to play N times. The default is 1. ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation the animation to set the play count of play\_count number of times to play this animation. Set to ANIMATION\_PLAY\_COUNT\_INFINITE to make an animation repeat indefinitely. #### Returns true if successful, false on failure [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) animation\_get\_play\_count(Animation \* animation) Get the play count of an animation. #### Parameters animation The animation for which to get the setting #### Returns the play count bool animation\_set\_duration(Animation \* animation, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) duration\_ms) Sets the time in milliseconds that an animation takes from start to finish. ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation The animation for which to set the duration. duration\_ms The duration in milliseconds of the animation. This excludes any optional delay as set using [animation\_set\_delay()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_delay). #### Returns true if successful, false on failure [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) animation\_get\_duration(Animation \* animation, bool include\_delay, bool include\_play\_count) Get the static duration of an animation from start to end (ignoring how much has already played, if any). #### Parameters animation The animation for which to get the duration include\_delay if true, include the delay time include\_play\_count if true, incorporate the play\_count #### Returns the duration, in milliseconds. This includes any optional delay a set using [animation\_set\_delay](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_delay). bool animation\_set\_delay(Animation \* animation, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) delay\_ms) Sets an optional delay for the animation. ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation The animation for which to set the delay. delay\_ms The delay in milliseconds that the animation system should wait from the moment the animation is scheduled to starting the animation. #### Returns true if successful, false on failure [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) animation\_get\_delay(Animation \* animation) Get the delay of an animation in milliseconds. #### Parameters animation The animation for which to get the setting #### Returns the delay in milliseconds bool animation\_set\_curve(Animation \* animation, [AnimationCurve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve) curve) Sets the animation curve for the animation. ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation The animation for which to set the curve. curve The type of curve. #### Returns true if successful, false on failure #### See Also [AnimationCurve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve) [AnimationCurve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve) animation\_get\_curve(Animation \* animation) Gets the animation curve for the animation. #### Parameters animation The animation for which to get the curve. #### Returns The type of curve. bool animation\_set\_custom\_curve(Animation \* animation, [AnimationCurveFunction](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveFunction) curve\_function) Sets a custom animation curve function. ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation The animation for which to set the curve. curve\_function The custom animation curve function. #### Returns true if successful, false on failure #### See Also [AnimationCurveFunction](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveFunction) [AnimationCurveFunction](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveFunction) animation\_get\_custom\_curve(Animation \* animation) Gets the custom animation curve function for the animation. #### Parameters animation The animation for which to get the curve. #### Returns The custom animation curve function for the given animation. NULL if not set. bool animation\_set\_handlers(Animation \* animation, [AnimationHandlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationHandlers) callbacks, void \* context) Sets the callbacks for the animation. Often an application needs to run code at the start or at the end of an animation. Using this function is possible to register callback functions with an animation, that will get called at the start and end of the animation. ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation The animation for which to set up the callbacks. callbacks The callbacks. context A pointer to application specific data, that will be passed as an argument by the animation subsystem when a callback is called. #### Returns true if successful, false on failure void \* animation\_get\_context(Animation \* animation) Gets the application-specific callback context of the animation. This `void` pointer is passed as an argument when the animation system calls [AnimationHandlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationHandlers) callbacks. The context pointer can be set to point to any application specific data using [animation\_set\_handlers()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers). #### Parameters animation The animation. #### See Also [animation\_set\_handlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers) bool animation\_schedule(Animation \* animation) Schedules the animation. Call this once after configuring an animation to get it to start running. If the animation's implementation has a `.setup` callback it will get called before this function returns. ##### Note If the animation was already scheduled, it will first unschedule it and then re-schedule it again. Note that in that case, the animation's `.stopped` handler, the implementation's `.teardown` and `.setup` will get called, due to the unscheduling and scheduling. #### Parameters animation The animation to schedule. #### Returns true if successful, false on failure #### See Also [animation\_unschedule()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_unschedule) bool animation\_unschedule(Animation \* animation) Unschedules the animation, which in effect stops the animation. ##### Notes If the animation was not yet finished, unscheduling it will cause its `.stopped` handler to get called, with the "finished" argument set to false. If the animation is not scheduled or NULL, calling this routine is effectively a no-op #### Parameters animation The animation to unschedule. #### Returns true if successful, false on failure #### See Also [animation\_schedule()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_schedule) void animation\_unschedule\_all(void) Unschedules all animations of the application. #### See Also [animation\_unschedule](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_unschedule) bool animation\_is\_scheduled(Animation \* animation) ##### Note An animation will be scheduled when it is running and not finished yet. An animation that has finished is automatically unscheduled. For convenience, passing in a NULL animation argument will simply return false #### Parameters animation The animation for which to get its scheduled state. #### Returns True if the animation was scheduled, or false if it was not. #### See Also [animation\_schedule](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_schedule) [animation\_unschedule](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_unschedule) bool animation\_set\_implementation(Animation \* animation, const [AnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationImplementation) \* implementation) Sets the implementation of the custom animation. When implementing custom animations, use this function to specify what functions need to be called to for the setup, frame update and teardown of the animation. ##### Note Trying to set an attribute when an animation is immutable will return false (failure). An animation is immutable once it has been added to a sequence or spawn animation or has been scheduled. #### Parameters animation The animation for which to set the implementation. implementation The structure with function pointers to the implementation of the setup, update and teardown functions. #### Returns true if successful, false on failure #### See Also [AnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationImplementation) const [AnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationImplementation) \* animation\_get\_implementation(Animation \* animation) Gets the implementation of the custom animation. #### Parameters animation The animation for which to get the implementation. #### Returns NULL if animation implementation has not been setup. #### See Also [AnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationImplementation) ## Data Structure Documentation struct AnimationHandlers The handlers that will get called when an animation starts and stops. See documentation with the function pointer types for more information. #### Data Fields [AnimationStartedHandler](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationStartedHandler) started The handler that will be called when an animation is started. [AnimationStoppedHandler](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationStoppedHandler) stopped The handler that will be called when an animation is stopped. #### See Also [animation\_set\_handlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers) struct AnimationImplementation The 3 callbacks that implement a custom animation. Only the `.update` callback is mandatory, `.setup` and `.teardown` are optional. See the documentation with the function pointer typedefs for more information. The diagram below illustrates the order in which callbacks can be expected to get called over the life cycle of an animation. It also illustrates where the implementation of different animation callbacks are intended to be “living”. ![](https://developer.repebble.com/assets/images/docs/c/aplite/animations.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/animations.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/animations.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/animations.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/animations.png) ##### Note The `.setup` callback is called immediately after scheduling the animation, regardless if there is a delay set for that animation using [animation\_set\_delay()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_delay). #### Data Fields [AnimationSetupImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationSetupImplementation) setup Called by the animation system when an animation is scheduled, to prepare it for running. This callback is optional and can be left `NULL` when not needed. [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) update Called by the animation system when the animation needs to calculate the next animation frame. This callback is mandatory and should not be left `NULL`. [AnimationTeardownImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationTeardownImplementation) teardown Called by the animation system when an animation is unscheduled, to clean up after it has run. This callback is optional and can be left `NULL` when not needed. #### See Also [AnimationSetupImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationSetupImplementation) [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) [AnimationTeardownImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationTeardownImplementation) ## Enum Documentation enum AnimationCurve Values that are used to indicate the different animation curves, which determine the speed at which the animated value(s) change(s). #### Enumerators AnimationCurveLinear Linear curve: the velocity is constant. AnimationCurveEaseIn Bicubic ease-in: accelerate from zero velocity. AnimationCurveEaseOut Bicubic ease-in: decelerate to zero velocity. AnimationCurveEaseInOut Bicubic ease-in-out: accelerate from zero velocity, decelerate to zero velocity. AnimationCurveDefaultAnimationCurveCustomFunction Custom (user-provided) animation curve. AnimationCurveCustomInterpolationFunction User-provided interpolation function. AnimationCurve\_Reserved1AnimationCurve\_Reserved2 ## Typedef Documentation typedef struct Animation Animation typedef int32\_t AnimationProgress The type used to represent how far an animation has progressed. This is passed to the animation's update handler. typedef [AnimationProgress](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationProgress)(\* AnimationCurveFunction)(AnimationProgress linear\_distance) The function pointer type of a custom animation curve. #### Parameters linear\_distance The linear normalized animation distance to be curved. #### See Also [animation\_set\_custom\_curve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_custom_curve) typedef void(\* AnimationStartedHandler)(Animation \*animation, void \*context) The function pointer type of the handler that will be called when an animation is started, just before updating the first frame of the animation. ##### Note This is called after any optional delay as set by [animation\_set\_delay()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_delay) has expired. #### Parameters animation The animation that was started. context The pointer to custom, application specific data, as set using [animation\_set\_handlers()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers) #### See Also [animation\_set\_handlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers) typedef void(\* AnimationStoppedHandler)(Animation \*animation, bool finished, void \*context) The function pointer type of the handler that will be called when the animation is stopped. ##### Note This animation (i.e.: the `animation` parameter) may be destroyed here. It is not recommended to unschedule or destroy a **different** Animation within this Animation's `stopped` handler. #### Parameters animation The animation that was stopped. finished True if the animation was stopped because it was finished normally, or False if the animation was stopped prematurely, because it was unscheduled before finishing. context The pointer to custom, application specific data, as set using [animation\_set\_handlers()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers) #### See Also [animation\_set\_handlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers) typedef void(\* AnimationSetupImplementation)(Animation \*animation) Pointer to function that (optionally) prepares the animation for running. This callback is called when the animation is added to the scheduler. #### Parameters animation The animation that needs to be set up. #### See Also [animation\_schedule](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_schedule) [AnimationTeardownImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationTeardownImplementation) typedef void(\* AnimationUpdateImplementation)(Animation \*animation, const AnimationProgress progress) Pointer to function that updates the animation according to the given normalized progress. This callback will be called repeatedly by the animation scheduler whenever the animation needs to be updated. #### Parameters animation The animation that needs to update; gets passed in by the animation framework. progress The current normalized progress; gets passed in by the animation framework for each animation frame. The value [ANIMATION\_NORMALIZED\_MIN](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MIN) represents the start and [ANIMATION\_NORMALIZED\_MAX](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MAX) represents the end. Values outside this range (generated by a custom curve function) can be used to implement features like a bounce back effect, where the progress exceeds the desired final value before returning to complete the animation. When using a system provided curve function, each frame during the animation will have a progress value between [ANIMATION\_NORMALIZED\_MIN](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MIN) and [ANIMATION\_NORMALIZED\_MAX](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MAX) based on the animation duration and the [AnimationCurve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve). For example, say an animation was scheduled at t = 1.0s, has a delay of 1.0s, a duration of 2.0s and a curve of AnimationCurveLinear. Then the .update callback will get called on t = 2.0s with distance\_normalized = [ANIMATION\_NORMALIZED\_MIN](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MIN). For each frame thereafter until t = 4.0s, the update callback will get called where distance\_normalized is ([ANIMATION\_NORMALIZED\_MIN](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MIN) - ((([ANIMATION\_NORMALIZED\_MAX](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MAX) - [ANIMATION\_NORMALIZED\_MIN](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MIN)) \* t) / duration)). Other system animation curve functions will result in a non-linear relation between distance\_normalized and time. typedef void(\* AnimationTeardownImplementation)(Animation \*animation) Pointer to function that (optionally) cleans up the animation. This callback is called when the animation is removed from the scheduler. In case the `.setup` implementation allocated any memory, this is a good place to release that memory again. #### Parameters animation The animation that needs to be teared down. #### See Also [animation\_unschedule](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_unschedule) [AnimationSetupImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationSetupImplementation) ## Macro Definition Documentation #define ANIMATION\_DURATION\_INFINITE UINT32\_MAX Constant to indicate "infinite" duration. This can be used with [animation\_set\_duration()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_duration) to indicate that the animation should run indefinitely. This is useful when implementing for example a frame-by-frame simulation that does not have a clear ending (e.g. a game). ##### Notes Note that `distance_normalized` parameter that is passed into the `.update` implementation is meaningless in when an infinite duration is used. This can be returned by animation\_get\_duration (if the play count is infinite) #define ANIMATION\_PLAY\_COUNT\_INFINITE UINT32\_MAX Constant to indicate infinite play count. Can be passed to [animation\_set\_play\_count()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_play_count) to repeat indefinitely. ##### Note This can be returned by [animation\_get\_play\_count()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_play_count). #define ANIMATION\_NORMALIZED\_MIN 0 The normalized distance at the start of the animation. #define ANIMATION\_NORMALIZED\_MAX 65535 The normalized distance at the end of the animation. --- # App Source: https://developer.repebble.com/docs/c/Foundation/App/ App entry point and event loop. App is a module that provides you with an event loop for your Pebble app. All interaction between Pebble apps and the underlying Pebble OS takes place through an event loop. Before calling the [app\_event\_loop()](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop) function, you subscribe to event services and implement event handlers. Each handler receives specific types of Events dispatched throughout the life of the Pebble watchapp. The [app\_event\_loop()](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop) function takes care of both waiting for new events to become available on the watchapp event bus and routing new events to the appropriate handler. [Event Service](https://developer.repebble.com/docs/c/Foundation/Event_Service.md) allows an app to directly register for different types of events. This function will block until the watchapp is ready to exit, and should be placed in the app's main() function. A watchapp typically configures and uses the [app\_event\_loop()](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop) as follows: ``` int main(void) {// do set up here// Enter the main event loop. This will block until the app is ready to exit.app\_event\_loop();// do clean up here} ``` ## Function Documentation void app\_event\_loop(void) The event loop for C apps, to be used in app's main(). Will block until the app is ready to exit. --- # App Communication Source: https://developer.repebble.com/docs/c/Foundation/App_Communication/ API for interacting with the Pebble communication subsystem. ## Function Documentation void app\_comm\_set\_sniff\_interval(const [SniffInterval](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#SniffInterval) interval) Set the Bluetooth module's sniff interval. The sniff interval will be restored to normal by the OS after the app's de-init handler is called. Set the sniff interval to normal whenever possible. [SniffInterval](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#SniffInterval) app\_comm\_get\_sniff\_interval(void) Get the Bluetooth module's sniff interval. #### Returns The [SniffInterval](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#SniffInterval) value corresponding to the current interval ## Enum Documentation enum SniffInterval Intervals during which the Bluetooth module may enter a low power mode. The sniff interval defines the period during which the Bluetooth module may not exchange (ACL) packets. The longer the sniff interval, the more time the Bluetooth module may spend in a low power mode. It may be necessary to reduce the sniff interval if an app requires reduced latency when sending messages. These settings have a dramatic effect on the Pebble's energy consumption. Use the normal sniff interval whenever possible. Note, however, that switching between modes increases power consumption during the process. Frequent switching between modes is thus discouraged. Ensure you do not drop to normal frequently. The Bluetooth module is a major consumer of the Pebble's energy. #### Enumerators SNIFF\_INTERVAL\_NORMAL Set the sniff interval to normal (power-saving) mode. SNIFF\_INTERVAL\_REDUCED Reduce the sniff interval to increase the responsiveness of the radio at the expense of increasing Bluetooth energy consumption by a multiple of 2-5 (very significant) --- # App Glance Source: https://developer.repebble.com/docs/c/Foundation/App_Glance/ API for the application to modify its "glance" i.e. app menu subtitle. Apps have the ability to show a temporary icon and subtitle in the app selection menu; this is known as a "glance". For example, the app might use it to display a preview of its current state such as the currently playing song or the number of pending notifications. The glance is loaded with a stack of [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) structs; only the latest slice is shown, and they are set to expire at a certain time to show the next slice in the stack. To update the glance with a stack of slices, you must define an [AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback) and give it to [app\_glance\_reload](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload). The implementation of your [AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback) should call [app\_glance\_add\_slice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice) to add slices to the glance. The main window's unload handler is usually a good place to call [app\_glance\_reload](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload). PBL\_PLATFORM\_APLITE does not support App Glance. Example code: ``` #if !PBL\_PLATFORM\_APLITEstatic void glance\_reload\_callback(AppGlanceReloadSession \*session, size\_t limit, void \*context) {AppGlanceSlice slice = {.expiration\_time = APP\_GLANCE\_SLICE\_NO\_EXPIRATION,.layout.icon = APP\_GLANCE\_SLICE\_DEFAULT\_ICON,.layout.subtitle\_template\_string = "hello!",};AppGlanceResult result = app\_glance\_add\_slice(session, slice);if (result != APP\_GLANCE\_RESULT\_SUCCESS){APP\_LOG(APP\_LOG\_LEVEL\_ERROR, "app\_glance\_add\_slice() returned %d", result);}}#endif // !PBL\_PLATFORM\_APLITEstatic void main\_window\_unload(Window \*window) {#if !PBL\_PLATFORM\_APLITEapp\_glance\_reload(glance\_reload\_callback, NULL);#endif // !PBL\_PLATFORM\_APLITE} ``` ## Function Documentation [AppGlanceResult](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceResult) app\_glance\_add\_slice(AppGlanceReloadSession \* session, [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) slice) Add a slice to the app's glance. This function will only succeed if called with a valid AppGlanceReloadSession that is provided in an [AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback). #### Parameters session The session variable provided in an [AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback) slice The slice to add to the app's glance #### Returns The result of trying to add the slice to the app's glance void app\_glance\_reload([AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback) callback, void \* context) Clear any existing slices in the app's glance and trigger a reload via the provided callback. #### Parameters callback A function that will be called to add new slices to the app's glance; even if the provided callback is NULL, any existing slices will still be cleared from the app's glance context User-provided context that will be passed to the callback ## Data Structure Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) struct AppGlanceSlice An app's glance can change over time as defined by zero or more app glance slices that each describe the state of the app glance at a particular point in time. Slices are displayed in the order they are added, and they are removed at the specified expiration time. #### Data Fields struct [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) layout Describes how the slice should be visualized in the app's glance in the launcher. [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) expiration\_time The UTC time after which this slice should no longer be shown in the app's glance. Use [APP\_GLANCE\_SLICE\_NO\_EXPIRATION](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_NO_EXPIRATION) if the slice should never expire. struct AppGlanceSlice An app's glance can change over time as defined by zero or more app glance slices that each describe the state of the app glance at a particular point in time. Slices are displayed in the order they are added, and they are removed at the specified expiration time. #### Data Fields struct [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) layout Describes how the slice should be visualized in the app's glance in the launcher. [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) expiration\_time The UTC time after which this slice should no longer be shown in the app's glance. Use [APP\_GLANCE\_SLICE\_NO\_EXPIRATION](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_NO_EXPIRATION) if the slice should never expire. struct AppGlanceSlice An app's glance can change over time as defined by zero or more app glance slices that each describe the state of the app glance at a particular point in time. Slices are displayed in the order they are added, and they are removed at the specified expiration time. #### Data Fields [AppGlanceSliceLayout](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSliceLayout) layout Describes how the slice should be visualized in the app's glance in the launcher. [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) expiration\_time The UTC time after which this slice should no longer be shown in the app's glance. Use [APP\_GLANCE\_SLICE\_NO\_EXPIRATION](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_NO_EXPIRATION) if the slice should never expire. struct AppGlanceSliceLayout Describes how the slice should be visualized in the app's glance in the launcher. #### Data Fields [PublishedId](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#PublishedId) icon The published resource ID of the bitmap icon to display in the app's glance. Use [APP\_GLANCE\_SLICE\_DEFAULT\_ICON](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_DEFAULT_ICON) to use the app's default bitmap icon. const char \* subtitle\_template\_string A template string to visualize in the app's glance. The string will be copied, so it is safe to destroy after adding the slice to the glance. Use NULL if no string should be displayed. ## Enum Documentation enum AppGlanceResult Bitfield enum describing the result of trying to add an [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) to an app's glance. #### Enumerators APP\_GLANCE\_RESULT\_SUCCESS The slice was successfully added to the app's glance. APP\_GLANCE\_RESULT\_INVALID\_TEMPLATE\_STRING The subtitle\_template\_string provided in the slice was invalid. APP\_GLANCE\_RESULT\_TEMPLATE\_STRING\_TOO\_LONG The subtitle\_template\_string provided in the slice was longer than 150 bytes. APP\_GLANCE\_RESULT\_INVALID\_ICON The icon provided in the slice was invalid. APP\_GLANCE\_RESULT\_SLICE\_CAPACITY\_EXCEEDED The provided slice would exceed the app glance's slice capacity. APP\_GLANCE\_RESULT\_EXPIRES\_IN\_THE\_PAST The expiration\_time provided in the slice expires in the past. APP\_GLANCE\_RESULT\_INVALID\_SESSION The AppGlanceReloadSession provided was invalid. ## Typedef Documentation typedef [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) PublishedId The ID of a published app resource defined within the publishedMedia section of package.json. typedef struct AppGlanceReloadSession AppGlanceReloadSession typedef void(\* AppGlanceReloadCallback)(AppGlanceReloadSession \*session, size\_t limit, void \*context) User-provided callback for reloading the slices in the app's glance. #### Parameters session A session variable that must be passed to [app\_glance\_add\_slice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice) when adding slices to the app's glance; it becomes invalid when the [AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback) returns limit The number of entries that can be added to the app's glance context User-provided context provided when calling [app\_glance\_reload()](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload) ## Macro Definition Documentation #define APP\_GLANCE\_SLICE\_NO\_EXPIRATION (([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t))0) Can be used for the expiration\_time of an [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) so that the slice never expires. #define APP\_GLANCE\_SLICE\_DEFAULT\_ICON (([PublishedId](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#PublishedId))0) Can be used for the icon of an [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) so that the slice displays the app's default icon. --- # AppFocusService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService/ Handling app focus The AppFocusService allows developers to be notified when their apps become visible on the screen. Common reasons your app may be running but not be on screen are: it's still in the middle of launching and being revealed by a system animation, or it is being covered by a system window such as a notification. This service is useful for apps that require a high degree of user interactivity, like a game where you'll want to pause when a notification covers your app window. It can be also used for apps that want to sync up an intro animation to the end of the system animation that occurs before your app is visible. ## Function Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void app\_focus\_service\_subscribe\_handlers([AppFocusHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers) handlers) Subscribe to the focus event service. Once subscribed, the handlers get called every time the app gains or loses focus. #### Parameters handler Handlers which will be called on will-focus and did-focus events. #### See Also [AppFocusHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers) void app\_focus\_service\_subscribe\_handlers([AppFocusHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers) handlers) Subscribe to the focus event service. Once subscribed, the handlers get called every time the app gains or loses focus. #### Parameters handler Handlers which will be called on will-focus and did-focus events. #### See Also [AppFocusHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers) void app\_focus\_service\_subscribe\_handlers([AppFocusHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers) handlers) Subscribe to the focus event service. Once subscribed, the handlers get called every time the app gains or loses focus. #### Parameters handlers Handlers which will be called on will-focus and did-focus events. #### See Also [AppFocusHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers) void app\_focus\_service\_subscribe([AppFocusHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandler) handler) Subscribe to the focus event service. Once subscribed, the handler gets called every time the app focus changes. ##### Notes Calling this function is equivalent to ``` app\_focus\_service\_subscribe\_handlers((AppFocusHandlers){.will\_focus = handler,}); ``` Out focus events are triggered when a modal window is about to open and cover the app. In focus events are triggered when a modal window which is covering the app is about to close. #### Parameters handler A callback to be called on will-focus events. void app\_focus\_service\_unsubscribe(void) Unsubscribe from the focus event service. Once unsubscribed, the previously registered handlers will no longer be called. ## Data Structure Documentation struct AppFocusHandlers There are two different focus events which take place when transitioning to and from an app being in focus. Below is an example of when these events will occur: 1) The app is launched. Once the system animation to the app has completed and the app is completely in focus, the did\_focus handler is called with in\_focus set to true. 2) A notification comes in and the animation to show the notification starts. The will\_focus handler is called with in\_focus set to false. 3) The animation completes and the notification is in focus, with the app being completely covered. The did\_focus hander is called with in\_focus set to false. 4) The notification is dismissed and the animation to return to the app starts. The will\_focus handler is called with in\_focus set to true. 5) The animation completes and the app is in focus. The did\_focus handler is called with in\_focus set to true. #### Data Fields [AppFocusHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandler) will\_focus Handler which will be called right before an app will lose or gain focus. ##### Notes This will be called with in\_focus set to true when a window which is covering the app is about to close and return focus to the app. This will be called with in\_focus set to false when a window which will cover the app is about to open, causing the app to lose focus. [AppFocusHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandler) did\_focus Handler which will be called when an animation finished which has put the app into focus or taken the app out of focus. ##### Notes This will be called with in\_focus set to true when a window which was covering the app has closed and the app has gained focus. This will be called with in\_focus set to false when a window has opened which is now covering the app, causing the app to lose focus. ## Typedef Documentation typedef void(\* AppFocusHandler)(bool in\_focus) Callback type for focus events. #### Parameters in\_focus True if the app is gaining focus, false otherwise. --- # AppMessage Source: https://developer.repebble.com/docs/c/Foundation/AppMessage/ Bi-directional communication between phone apps and Pebble watchapps AppMessage is a bi-directional messaging subsystem that enables communication between phone apps and Pebble watchapps. This is accomplished by allowing phone and watchapps to exchange arbitrary sets of key/value pairs. The key/value pairs are stored in the form of a Dictionary, the layout of which is left for the application developer to define. AppMessage implements a push-oriented messaging protocol, enabling your app to call functions and methods to push messages from Pebble to phone and vice versa. The protocol is symmetric: both Pebble and the phone can send messages. All messages are acknowledged. In this context, there is no client-server model, as such. During the sending phase, one side initiates the communication by transferring a dictionary over the air. The other side then receives this message and is given an opportunity to perform actions on that data. As soon as possible, the other side is expected to reply to the message with a simple acknowledgment that the message was received successfully. PebbleKit JavaScript provides you with a set of standard JavaScript APIs that let your app receive messages from the watch, make HTTP requests, and send new messages to the watch. AppMessage APIs are used to send and receive data. A Pebble watchapp can use the resources of the connected phone to fetch information from web services, send information to web APIs, or store login credentials. On the JavaScript side, you communicate with Pebble via a Pebble object exposed in the namespace. Messages always need to get either ACKnowledged or "NACK'ed," that is, not acknowledged. If not, messages will result in a time-out failure. The AppMessage subsystem takes care of this implicitly. In the phone libraries, this step is a bit more explicit. The Pebble watch interfaces make a distinction between the Inbox and the Outbox calls. The Inbox receives messages from the phone on the watch; the Outbox sends messages from the watch to the phone. These two buffers can be managed separately. #### Warning A critical constraint of AppMessage is that messages are limited in size. An ingoing (outgoing) message larger than the inbox (outbox) will not be transmitted and will generate an error. You can choose your inbox and outbox size when you call [app\_message\_open()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_open). Pebble SDK provides a static minimum guaranteed size (APP\_MESSAGE\_INBOX\_SIZE\_MINIMUM and APP\_MESSAGE\_OUTBOX\_SIZE\_MINIMUM). Requesting a buffer of the minimum guaranteed size (or smaller) is always guaranteed to succeed on all Pebbles in this SDK version or higher, and with every phone. In some context, Pebble might be able to provide your application with larger inbox/outbox. You can call [app\_message\_inbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_inbox_size_maximum) and [app\_message\_outbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_size_maximum) in your code to get the largest possible value you can use. To always get the largest buffer available, follow this best practice: app\_message\_open([app\_message\_inbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_inbox_size_maximum), [app\_message\_outbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_size_maximum)) AppMessage uses your application heap space. That means that the sizes you pick for the AppMessage inbox and outbox buffers are important in optimizing your app’s performance. The more you use for AppMessage, the less space you’ll have for the rest of your app. To register callbacks, you should call [app\_message\_register\_inbox\_received()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_inbox_received), [app\_message\_register\_inbox\_dropped()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_inbox_dropped), [app\_message\_register\_outbox\_sent()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_outbox_sent), [app\_message\_register\_outbox\_failed()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_outbox_failed). Pebble recommends that you call them before [app\_message\_open()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_open) to ensure you do not miss a message arriving between starting AppMessage and registering the callback. You can set a context that will be passed to all the callbacks with [app\_message\_set\_context()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_set_context). In circumstances that may not be ideal, when using AppMessage several types of errors may occur. For example: - The send can’t start because the system state won't allow for a success. Several reasons you're unable to perform a send: A send() is already occurring (only one is possible at a time) or Bluetooth is not enabled or connected. - The send and receive occur, but the receiver can’t accept the message. For instance, there is no app that receives such a message. - The send occurs, but the receiver either does not actually receive the message or can’t handle it in a timely fashion. - In the case of a dropped message, the phone sends a message to the watchapp, while there is still an unprocessed message in the Inbox. Other errors are possible and described by [AppMessageResult](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult). A client of the AppMessage interface should use the result codes to be more robust in the face of communication problems either in the field or while debugging. Refer to the [App Communication in the Pebble Developer Guides](https://developer.repebble.com/guides/pebble-apps/communications/) for a conceptual overview and code usage. For code examples, refer to the SDK Examples that directly use App Message. These include: - [pebblekit-js-weather](https://github.com/pebble-examples/pebblekit-js-weather) - [pebblekit-js-quotes](https://github.com/pebble-examples/pebblekit-js-quotes) ## Function Documentation [AppMessageResult](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult) app\_message\_open(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) size\_inbound, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) size\_outbound) Open AppMessage to transfers. Use [dict\_calc\_buffer\_size\_from\_tuplets()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_calc_buffer_size_from_tuplets) or [dict\_calc\_buffer\_size()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_calc_buffer_size) to estimate the size you need. ##### Note It is recommended that if the Inbox will be used, that at least the Inbox callbacks should be registered before this call. Otherwise it is possible for an Inbox message to be NACK'ed without being seen by the application. #### Parameters size\_inbound (in) The required size for the Inbox buffer size\_outbound (in) The required size for the Outbox buffer #### Returns A result code such as APP\_MSG\_OK or APP\_MSG\_OUT\_OF\_MEMORY. void app\_message\_deregister\_callbacks(void) Deregisters all callbacks and their context. void \* app\_message\_get\_context(void) Gets the context that will be passed to all AppMessage callbacks. #### Returns The current context on record. void \* app\_message\_set\_context(void \* context) Sets the context that will be passed to all AppMessage callbacks. #### Parameters context (in) The context that will be passed to all AppMessage callbacks. #### Returns The previous context that was on record. [AppMessageInboxReceived](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived) app\_message\_register\_inbox\_received([AppMessageInboxReceived](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived) received\_callback) Registers a function that will be called after any Inbox message is received successfully. Only one callback may be registered at a time. Each subsequent call to this function will replace the previous callback. The callback is optional; setting it to NULL will deregister the current callback and no function will be called anymore. #### Parameters received\_callback (in) The callback that will be called going forward; NULL to not have a callback. #### Returns The previous callback (or NULL) that was on record. [AppMessageInboxDropped](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxDropped) app\_message\_register\_inbox\_dropped([AppMessageInboxDropped](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxDropped) dropped\_callback) Registers a function that will be called after any Inbox message is received but dropped by the system. Only one callback may be registered at a time. Each subsequent call to this function will replace the previous callback. The callback is optional; setting it to NULL will deregister the current callback and no function will be called anymore. #### Parameters dropped\_callback (in) The callback that will be called going forward; NULL to not have a callback. #### Returns The previous callback (or NULL) that was on record. [AppMessageOutboxSent](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent) app\_message\_register\_outbox\_sent([AppMessageOutboxSent](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent) sent\_callback) Registers a function that will be called after any Outbox message is sent and an ACK reply occurs in a timely fashion. Only one callback may be registered at a time. Each subsequent call to this function will replace the previous callback. The callback is optional; setting it to NULL will deregister the current callback and no function will be called anymore. #### Parameters sent\_callback (in) The callback that will be called going forward; NULL to not have a callback. #### Returns The previous callback (or NULL) that was on record. [AppMessageOutboxFailed](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxFailed) app\_message\_register\_outbox\_failed([AppMessageOutboxFailed](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxFailed) failed\_callback) Registers a function that will be called after any Outbox message is not sent with a timely ACK reply. The call to [app\_message\_outbox\_send()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_send) must have succeeded. Only one callback may be registered at a time. Each subsequent call to this function will replace the previous callback. The callback is optional; setting it to NULL will deregister the current callback and no function will be called anymore. #### Parameters failed\_callback (in) The callback that will be called going forward; NULL to not have a callback. #### Returns The previous callback (or NULL) that was on record. [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) app\_message\_inbox\_size\_maximum(void) Programatically determine the inbox size maximum in the current configuration. #### Returns The inbox size maximum on this firmware. #### See Also [APP\_MESSAGE\_INBOX\_SIZE\_MINIMUM](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MESSAGE_INBOX_SIZE_MINIMUM) [app\_message\_outbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_size_maximum) [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) app\_message\_outbox\_size\_maximum(void) Programatically determine the outbox size maximum in the current configuration. #### Returns The outbox size maximum on this firmware. #### See Also [APP\_MESSAGE\_OUTBOX\_SIZE\_MINIMUM](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MESSAGE_OUTBOX_SIZE_MINIMUM) [app\_message\_inbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_inbox_size_maximum) [AppMessageResult](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult) app\_message\_outbox\_begin([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \*\* iterator) Begin writing to the Outbox's Dictionary buffer. ##### Note After a successful call, one can add values to the dictionary using functions like [dict\_write\_data()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_data) and friends. #### Parameters iterator (out) Location to write the [DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) pointer. This will be NULL on failure. #### Returns A result code, including but not limited to APP\_MSG\_OK, APP\_MSG\_INVALID\_ARGS or APP\_MSG\_BUSY. #### See Also [Dictionary](https://developer.repebble.com/docs/c/Foundation/Dictionary.md) [AppMessageResult](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult) app\_message\_outbox\_send(void) Sends the outbound dictionary. #### Returns A result code, including but not limited to APP\_MSG\_OK or APP\_MSG\_BUSY. The APP\_MSG\_OK code does not mean that the message was sent successfully, but only that the start of processing was successful. Since this call is asynchronous, callbacks provide the final result instead. #### See Also [AppMessageOutboxSent](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent) [AppMessageOutboxFailed](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxFailed) ## Enum Documentation enum AppMessageResult AppMessage result codes. #### Enumerators APP\_MSG\_OK (0) All good, operation was successful. APP\_MSG\_SEND\_TIMEOUT (2) The other end did not confirm receiving the sent data with an (n)ack in time. APP\_MSG\_SEND\_REJECTED (4) The other end rejected the sent data, with a "nack" reply. APP\_MSG\_NOT\_CONNECTED (8) The other end was not connected. APP\_MSG\_APP\_NOT\_RUNNING (16) The local application was not running. APP\_MSG\_INVALID\_ARGS (32) The function was called with invalid arguments. APP\_MSG\_BUSY (64) There are pending (in or outbound) messages that need to be processed first before new ones can be received or sent. APP\_MSG\_BUFFER\_OVERFLOW (128) The buffer was too small to contain the incoming message. APP\_MSG\_ALREADY\_RELEASED (512) The resource had already been released. APP\_MSG\_CALLBACK\_ALREADY\_REGISTERED (1024) The callback was already registered. APP\_MSG\_CALLBACK\_NOT\_REGISTERED (2048) The callback could not be deregistered, because it had not been registered before. APP\_MSG\_OUT\_OF\_MEMORY (4096) The system did not have sufficient application memory to perform the requested operation. APP\_MSG\_CLOSED (8192) App message was closed. APP\_MSG\_INTERNAL\_ERROR (16384) An internal OS error prevented AppMessage from completing an operation. APP\_MSG\_INVALID\_STATE (32768) The function was called while App Message was not in the appropriate state. ## Typedef Documentation typedef void(\* AppMessageInboxReceived)(DictionaryIterator \*iterator, void \*context) Called after an incoming message is received. #### Parameters iterator (in) The dictionary iterator to the received message. Never NULL. Note that the iterator cannot be modified or saved off. The library may need to re-use the buffered space where this message is supplied. Returning from the callback indicates to the library that the received message contents are no longer needed or have already been externalized outside its buffering space and iterator. context (in) Pointer to application data as specified when registering the callback. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) typedef void(\* AppMessageInboxDropped)(AppMessageResult reason, void \*context) Called after an incoming message is dropped. Note that you can call [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin) from this handler to prepare a new message. This will invalidate the previous dictionary iterator; do not use it after calling [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin). #### Parameters result (in) The reason why the message was dropped. Some possibilities include APP\_MSG\_BUSY and APP\_MSG\_BUFFER\_OVERFLOW. context (in) Pointer to application data as specified when registering the callback. typedef void(\* AppMessageInboxDropped)(AppMessageResult reason, void \*context) Called after an incoming message is dropped. Note that you can call [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin) from this handler to prepare a new message. This will invalidate the previous dictionary iterator; do not use it after calling [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin). #### Parameters result (in) The reason why the message was dropped. Some possibilities include APP\_MSG\_BUSY and APP\_MSG\_BUFFER\_OVERFLOW. context (in) Pointer to application data as specified when registering the callback. typedef void(\* AppMessageInboxDropped)(AppMessageResult reason, void \*context) Called after an incoming message is dropped. Note that you can call [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin) from this handler to prepare a new message. This will invalidate the previous dictionary iterator; do not use it after calling [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin). #### Parameters reason (in) The reason why the message was dropped. Some possibilities include APP\_MSG\_BUSY and APP\_MSG\_BUFFER\_OVERFLOW. context (in) Pointer to application data as specified when registering the callback. typedef void(\* AppMessageOutboxSent)(DictionaryIterator \*iterator, void \*context) Called after an outbound message has been sent and the reply has been received. #### Parameters iterator (in) The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that the iterator cannot be modified or saved off as the library will re-open the dictionary with dict\_begin() after this callback returns. context (in) Pointer to application data as specified when registering the callback. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) typedef void(\* AppMessageOutboxFailed)(DictionaryIterator \*iterator, AppMessageResult reason, void \*context) Called after an outbound message has not been sent successfully. Note that you can call [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin) from this handler to prepare a new message. This will invalidate the previous dictionary iterator; do not use it after calling [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin). #### Parameters iterator (in) The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that the iterator cannot be modified or saved off as the library will re-open the dictionary with dict\_begin() after this callback returns. result (in) The result of the operation. Some possibilities for the value include APP\_MSG\_SEND\_TIMEOUT, APP\_MSG\_SEND\_REJECTED, APP\_MSG\_NOT\_CONNECTED, APP\_MSG\_APP\_NOT\_RUNNING, and the combination `(APP_MSG_NOT_CONNECTED | APP_MSG_APP_NOT_RUNNING)`. context Pointer to application data as specified when registering the callback. typedef void(\* AppMessageOutboxFailed)(DictionaryIterator \*iterator, AppMessageResult reason, void \*context) Called after an outbound message has not been sent successfully. Note that you can call [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin) from this handler to prepare a new message. This will invalidate the previous dictionary iterator; do not use it after calling [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin). #### Parameters iterator (in) The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that the iterator cannot be modified or saved off as the library will re-open the dictionary with dict\_begin() after this callback returns. result (in) The result of the operation. Some possibilities for the value include APP\_MSG\_SEND\_TIMEOUT, APP\_MSG\_SEND\_REJECTED, APP\_MSG\_NOT\_CONNECTED, APP\_MSG\_APP\_NOT\_RUNNING, and the combination `(APP_MSG_NOT_CONNECTED | APP_MSG_APP_NOT_RUNNING)`. context Pointer to application data as specified when registering the callback. typedef void(\* AppMessageOutboxFailed)(DictionaryIterator \*iterator, AppMessageResult reason, void \*context) Called after an outbound message has not been sent successfully. Note that you can call [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin) from this handler to prepare a new message. This will invalidate the previous dictionary iterator; do not use it after calling [app\_message\_outbox\_begin()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin). #### Parameters iterator (in) The dictionary iterator to the sent message. The iterator will be in the final state that was sent. Note that the iterator cannot be modified or saved off as the library will re-open the dictionary with dict\_begin() after this callback returns. reason (in) The result of the operation. Some possibilities for the value include APP\_MSG\_SEND\_TIMEOUT, APP\_MSG\_SEND\_REJECTED, APP\_MSG\_NOT\_CONNECTED, APP\_MSG\_APP\_NOT\_RUNNING, and the combination `(APP_MSG_NOT_CONNECTED | APP_MSG_APP_NOT_RUNNING)`. context Pointer to application data as specified when registering the callback. ## Macro Definition Documentation #define APP\_MESSAGE\_INBOX\_SIZE\_MINIMUM 124 As long as the firmware maintains its current major version, inboxes of this size or smaller will be allowed. #### See Also [app\_message\_inbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_inbox_size_maximum) [APP\_MESSAGE\_OUTBOX\_SIZE\_MINIMUM](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MESSAGE_OUTBOX_SIZE_MINIMUM) #define APP\_MESSAGE\_OUTBOX\_SIZE\_MINIMUM 636 As long as the firmware maintains its current major version, outboxes of this size or smaller will be allowed. #### See Also [app\_message\_outbox\_size\_maximum()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_size_maximum) [APP\_MESSAGE\_INBOX\_SIZE\_MINIMUM](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MESSAGE_INBOX_SIZE_MINIMUM) --- # AppSync Source: https://developer.repebble.com/docs/c/Foundation/AppSync/ UI synchronization layer for AppMessage AppSync is a convenience layer that resides on top of [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md), and serves as a UI synchronization layer for AppMessage. In so doing, AppSync makes it easier to drive the information displayed in the watchapp UI with messages sent by a phone app. AppSync maintains and updates a Dictionary, and provides your app with a callback ([AppSyncTupleChangedCallback](https://developer.repebble.com/docs/c/Foundation/AppSync.md#AppSyncTupleChangedCallback)) routine that is called whenever the Dictionary changes and the app's UI is updated. Note that the app UI is not updated automatically. To update the UI, you need to implement the callback. Pebble OS provides support for data serialization utilities, like Dictionary, [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) and [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) data structures and their accompanying functions. You use Tuplets to create a Dictionary with [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) structures. AppSync manages the storage and bookkeeping chores of the current [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) values. AppSync copies incoming AppMessage Tuples into this "current" Dictionary, so that the key/values remain available for the UI to use. For example, it is safe to use a C-string value provided by AppSync and use it directly in a [text\_layer\_set\_text()](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text) call. Your app needs to supply the buffer that AppSync uses for the "current" Dictionary when initializing AppSync. Refer to the [Synchronizing App UI](https://developer.repebble.com/guides/pebble-apps/communications/appsync/) guide for a conceptual overview and code usage. ## Function Documentation void app\_sync\_init(struct AppSync \* s, uint8\_t \* buffer, const [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) buffer\_size, const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const keys\_and\_initial\_values, const uint8\_t count, [AppSyncTupleChangedCallback](https://developer.repebble.com/docs/c/Foundation/AppSync.md#AppSyncTupleChangedCallback) tuple\_changed\_callback, [AppSyncErrorCallback](https://developer.repebble.com/docs/c/Foundation/AppSync.md#AppSyncErrorCallback) error\_callback, void \* context) Initialized an AppSync system with specific buffer size and initial keys and values. The `callback.value_changed` callback will be called **asynchronously** with the initial keys and values, as to avoid duplicating code to update your app's UI. ##### Note Only updates for the keys specified in this initial array will be accepted by AppSync, updates for other keys that might come in will just be ignored. #### Parameters s The AppSync context to initialize buffer The buffer that AppSync should use buffer\_size The size of the backing storage of the "current" dictionary. Use [dict\_calc\_buffer\_size\_from\_tuplets()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_calc_buffer_size_from_tuplets) to estimate the size you need. keys\_and\_initial\_values An array of Tuplets with the initial keys and values. count The number of Tuplets in the `keys_and_initial_values` array. tuple\_changed\_callback The callback that will handle changed key/value pairs error\_callback The callback that will handle errors context Pointer to app specific data that will get passed into calls to the callbacks void app\_sync\_deinit(struct AppSync \* s) Cleans up an AppSync system. It frees the buffer allocated by an [app\_sync\_init()](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_init) call and deregisters itself from the [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md) subsystem. #### Parameters s The AppSync context to deinit. [AppMessageResult](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult) app\_sync\_set(struct AppSync \* s, const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const keys\_and\_values\_to\_update, const uint8\_t count) Updates key/value pairs using an array of Tuplets. ##### Note The call will attempt to send the updated keys and values to the application on the other end. Only after the other end has acknowledged the update, the `.value_changed` callback will be called to confirm the update has completed and your application code can update its user interface. #### Parameters s The AppSync context keys\_and\_values\_to\_update An array of Tuplets with the keys and values to update. The data in the Tuplets are copied during the call, so the array can be stack-allocated. count The number of Tuplets in the `keys_and_values_to_update` array. #### Returns The result code from the [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md) subsystem. Can be APP\_MSG\_OK, APP\_MSG\_BUSY or APP\_MSG\_INVALID\_ARGS const [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) \* app\_sync\_get(const struct AppSync \* s, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Finds and gets a tuple in the "current" dictionary. #### Parameters s The AppSync context key The key for which to find a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) #### Returns Pointer to a found [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple), or NULL if there was no [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) with the specified key. ## Data Structure Documentation struct AppSync #### Data Fields [DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) current\_iterunion AppSync [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) buffer\_sizestruct AppSync callback ## Typedef Documentation typedef void(\* AppSyncTupleChangedCallback)(const uint32\_t key, const Tuple \*new\_tuple, const Tuple \*old\_tuple, void \*context) Called whenever a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) changes. This does not necessarily mean the value in the [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) has changed. When the internal "current" dictionary gets updated, existing Tuples might get shuffled around in the backing buffer, even though the values stay the same. In this callback, the client code gets the chance to remove the old reference and start using the new one. In this callback, your application MUST clean up any references to the `old_tuple` of a PREVIOUS call to this callback (and replace it with the `new_tuple` that is passed in with the current call). #### Parameters key The key for which the [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) was changed. new\_tuple The new tuple. The tuple points to the actual, updated "current" dictionary, as backed by the buffer internal to the AppSync struct. Therefore the [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) can be used after the callback returns, until the AppSync is deinited. In case there was an error (e.g. storage shortage), this `new_tuple` can be `NULL_TUPLE`. old\_tuple The values that will be replaced with `new_tuple`. The key, value and type will be equal to the previous tuple in the old destination dictionary; however, the `old_tuple` points to a stack-allocated copy of the old data. This value will be `NULL_TUPLE` when the initial values are being set. context Pointer to application specific data, as set using [app\_sync\_init()](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_init) #### See Also [app\_sync\_init()](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_init) typedef void(\* AppSyncErrorCallback)(DictionaryResult dict\_error, AppMessageResult app\_message\_error, void \*context) Called whenever there was an error. #### Parameters dict\_error The dictionary result error code, if the error was dictionary related. app\_message\_error The app\_message result error code, if the error was app\_message related. context Pointer to application specific data, as set using [app\_sync\_init()](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_init) #### See Also [app\_sync\_init()](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_init) --- # AppWorker Source: https://developer.repebble.com/docs/c/Foundation/AppWorker/ Runs in the background, and can communicate with the foreground app. ## Function Documentation bool app\_worker\_is\_running(void) Determine if the worker for the current app is running. #### Returns true if running [AppWorkerResult](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerResult) app\_worker\_launch(void) Launch the worker for the current app. Note that this is an asynchronous operation, a result code of APP\_WORKER\_RESULT\_SUCCESS merely means that the request was successfully queued up. #### Returns result code [AppWorkerResult](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerResult) app\_worker\_kill(void) Kill the worker for the current app. Note that this is an asynchronous operation, a result code of APP\_WORKER\_RESULT\_SUCCESS merely means that the request was successfully queued up. #### Returns result code bool app\_worker\_message\_subscribe([AppWorkerMessageHandler](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerMessageHandler) handler) Subscribe to worker messages. Once subscribed, the handler gets called on every message emitted by the other task (either worker or app). #### Parameters handler A callback to be executed when the event is received #### Returns true on success bool app\_worker\_message\_unsubscribe(void) Unsubscribe from worker messages. Once unsubscribed, the previously registered handler will no longer be called. #### Returns true on success void app\_worker\_send\_message(uint8\_t type, [AppWorkerMessage](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerMessage) \* data) Send a message to the other task (either worker or app). #### Parameters type An application defined message type data the message data structure ## Data Structure Documentation struct AppWorkerMessage Generic structure of a worker message that can be sent between an app and its worker. #### Data Fields [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) data0[uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) data1[uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) data2 ## Enum Documentation enum AppWorkerResult Possible error codes from app\_worker\_launch, app\_worker\_kill. #### Enumerators APP\_WORKER\_RESULT\_SUCCESS Success. APP\_WORKER\_RESULT\_NO\_WORKER No worker found for the current app. APP\_WORKER\_RESULT\_DIFFERENT\_APP A worker for a different app is already running. APP\_WORKER\_RESULT\_NOT\_RUNNING The worker is not running. APP\_WORKER\_RESULT\_ALREADY\_RUNNING The worker is already running. APP\_WORKER\_RESULT\_ASKING\_CONFIRMATION The user will be asked for confirmation. ## Typedef Documentation typedef void(\* AppWorkerMessageHandler)(uint16\_t type, AppWorkerMessage \*data) Callback type for worker messages. Messages can be sent from worker to app or vice versa. #### Parameters type An application defined message type data pointer to message data. The receiver must know the structure of the data provided by the sender. --- # BacklightService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService/ The APIs on this page will only work with SDK 4.9+. Notifies your app when the backlight turns on or off. The BacklightService lets your app react to the backlight turning on. The handler is invoked whenever the backlight transitions between off and on (any non-off state — fully on, on with timeout, or fading out is treated as "on"). This means you get a single edge per wake, not one event per fade step. ## Function Documentation void backlight\_service\_subscribe([BacklightHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#BacklightHandler) handler) Subscribe to the backlight event service. Once subscribed, the handler is called every time the backlight transitions between off and on. #### Parameters handler A callback to be executed on backlight on/off events. void backlight\_service\_unsubscribe(void) Unsubscribe from the backlight event service. Once unsubscribed, the previously registered handler will no longer be called. To read the current backlight state at any time, use [light\_is\_on()](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_is_on). ## Typedef Documentation typedef void(\* BacklightHandler)(bool on) Callback type for backlight on/off events. #### Parameters on true when the backlight has just turned on, false when it has just turned fully off. --- # BatteryStateService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService/ Determines when the battery state changes The BatteryStateService API lets you know when the battery state changes, that is, its current charge level, whether it is plugged and charging. It uses the [BatteryChargeState](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryChargeState) structure to describe the current power state of Pebble. Refer to the [classio-battery-connection](https://github.com/pebble-examples/classio-battery-connection) example, which demonstrates using the battery state service in a watchface. ## Function Documentation void battery\_state\_service\_subscribe([BatteryStateHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryStateHandler) handler) Subscribe to the battery state event service. Once subscribed, the handler gets called on every battery state change. #### Parameters handler A callback to be executed on battery state change event void battery\_state\_service\_unsubscribe(void) Unsubscribe from the battery state event service. Once unsubscribed, the previously registered handler will no longer be called. [BatteryChargeState](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryChargeState) battery\_state\_service\_peek(void) Peek at the last known battery state. #### Returns a [BatteryChargeState](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryChargeState) containing the last known data ## Data Structure Documentation struct BatteryChargeState Structure for retrieval of the battery charge state. #### Data Fields uint8\_t charge\_percent A percentage (0-100) of how full the battery is. bool is\_charging True if the battery is currently being charged. False if not. bool is\_plugged True if the charger cable is connected. False if not. ## Typedef Documentation typedef void(\* BatteryStateHandler)(BatteryChargeState charge) Callback type for battery state change events. #### Parameters charge the state of the battery [BatteryChargeState](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryChargeState) --- # BitmapLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer/ Layer that displays a bitmap image. ![](https://developer.repebble.com/assets/images/docs/c/aplite/bitmap_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/bitmap_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/bitmap_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/bitmap_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/bitmap_layer.png) BitmapLayer is a Layer subtype that draws a GBitmap within its frame. It uses an alignment property to specify how to position the bitmap image within its frame. Optionally, when the background color is not GColorClear, it draws a solid background color behind the bitmap image, filling areas of the frame that are not covered by the bitmap image. Lastly, using the compositing mode property of the BitmapLayer, determines the way the bitmap image is drawn on top of what is underneath it (either the background color, or the layers beneath it). ### Inside the Implementation The implementation of BitmapLayer is fairly straightforward and relies heavily on the functionality as exposed by the core drawing functions (see [Drawing Primitives](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md)). [BitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md)'s drawing callback uses [graphics\_draw\_bitmap\_in\_rect()](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_bitmap_in_rect) to perform the actual drawing of the GBitmap. It uses [grect\_align()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_align) to perform the layout of the image and it uses [graphics\_fill\_rect()](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_rect) to draw the background plane. ## Function Documentation BitmapLayer \* bitmap\_layer\_create([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame) Creates a new bitmap layer on the heap and initalizes it the default values. - Bitmap: `NULL` (none) - Background color: GColorClear - Compositing mode: GCompOpAssign - Clips: `true` #### Returns A pointer to the BitmapLayer. `NULL` if the BitmapLayer could not be created void bitmap\_layer\_destroy(BitmapLayer \* bitmap\_layer) Destroys a window previously created by bitmap\_layer\_create. Layer \* bitmap\_layer\_get\_layer(const BitmapLayer \* bitmap\_layer) Gets the "root" Layer of the bitmap layer, which is the parent for the sub- layers used for its implementation. #### Parameters bitmap\_layer Pointer to the BitmapLayer for which to get the "root" Layer #### Returns The "root" Layer of the bitmap layer. const GBitmap \* bitmap\_layer\_get\_bitmap(BitmapLayer \* bitmap\_layer) Gets the pointer to the bitmap image that the BitmapLayer is using. #### Parameters bitmap\_layer The BitmapLayer for which to get the bitmap image #### Returns A pointer to the bitmap image that the BitmapLayer is using void bitmap\_layer\_set\_bitmap(BitmapLayer \* bitmap\_layer, const GBitmap \* bitmap) Sets the bitmap onto the BitmapLayer. The bitmap is set by reference (no deep copy), thus the caller of this function has to make sure the bitmap is kept in memory. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the bitmap image bitmap The new GBitmap to set onto the BitmapLayer void bitmap\_layer\_set\_alignment(BitmapLayer \* bitmap\_layer, [GAlign](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlign) alignment) Sets the alignment of the image to draw with in frame of the BitmapLayer. The aligment parameter specifies which edges of the bitmap should overlap with the frame of the BitmapLayer. If the bitmap is smaller than the frame of the BitmapLayer, the background is filled with the background color. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the aligment alignment The new alignment for the image inside the BitmapLayer - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void bitmap\_layer\_set\_background\_color(BitmapLayer \* bitmap\_layer, GColor color) Sets the background color of bounding box that will be drawn behind the image of the BitmapLayer. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the background color color The new GColor to set the background to void bitmap\_layer\_set\_background\_color(BitmapLayer \* bitmap\_layer, GColor color) Sets the background color of bounding box that will be drawn behind the image of the BitmapLayer. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the background color color The new GColor to set the background to void bitmap\_layer\_set\_background\_color(BitmapLayer \* bitmap\_layer, GColor color) Sets the background color of bounding box that will be drawn behind the image of the BitmapLayer. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the background color color The new GColor to set the background to #### See Also [bitmap\_layer\_set\_compositing\_mode](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_compositing_mode) - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void bitmap\_layer\_set\_compositing\_mode(BitmapLayer \* bitmap\_layer, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the compositing mode of how the bitmap image is composited onto the BitmapLayer's background plane, or how it is composited onto what has been drawn beneath the BitmapLayer. The compositing mode only affects the drawing of the bitmap and not the drawing of the background color. For Aplite, there is no notion of "transparency" in the graphics system. However, the effect of transparency can be created by masking and using compositing modes. For Basalt, when drawing GBitmap images, GCompOpSet will be required to apply any transparency. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the compositing mode mode The compositing mode to set #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) void bitmap\_layer\_set\_compositing\_mode(BitmapLayer \* bitmap\_layer, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the compositing mode of how the bitmap image is composited onto the BitmapLayer's background plane, or how it is composited onto what has been drawn beneath the BitmapLayer. The compositing mode only affects the drawing of the bitmap and not the drawing of the background color. For Aplite, there is no notion of "transparency" in the graphics system. However, the effect of transparency can be created by masking and using compositing modes. For Basalt, when drawing GBitmap images, GCompOpSet will be required to apply any transparency. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the compositing mode mode The compositing mode to set #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) void bitmap\_layer\_set\_compositing\_mode(BitmapLayer \* bitmap\_layer, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the compositing mode of how the bitmap image is composited onto the BitmapLayer's background plane, or how it is composited onto what has been drawn beneath the BitmapLayer. The compositing mode only affects the drawing of the bitmap and not the drawing of the background color. For black&white platforms, there is no notion of "transparency" in the graphics system. However, the effect of transparency can be created by masking and using compositing modes. For color platforms, when drawing GBitmap images, GCompOpSet is required to apply any transparency. The bitmap layer is automatically marked dirty after this operation. #### Parameters bitmap\_layer The BitmapLayer for which to set the compositing mode mode The compositing mode to set #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) ## Typedef Documentation typedef struct BitmapLayer BitmapLayer --- # C SDK Documentation Source: https://developer.repebble.com/docs/c/ This is the contents page for the [Pebble C SDK](https://developer.repebble.com/sdk.md). Here you will find information on all the available modules, functions and objects that can be used to create watchapps and watchfaces for Pebble. The C SDK is broken down into these 5 top level modules. Click on them to browse around the rest of the SDK. You can also use the search bar at the top of the page to find what you are looking for. #### [Foundation](https://developer.repebble.com/docs/c/Foundation.md) The core of the Pebble SDK. #### [Graphics](https://developer.repebble.com/docs/c/Graphics.md) Low-level drawing routines. #### [User Interface](https://developer.repebble.com/docs/c/User_Interface.md) Everything related to user interface. #### [Worker](https://developer.repebble.com/docs/c/Worker.md) Run a background worker alongside the foreground app to collect data or respond to events while the app is not running. See the [Background Worker](https://developer.repebble.com/guides/events-and-services/background-worker.md) guide. #### [Standard C](https://developer.repebble.com/docs/c/Standard_C.md)   --- # Clicks Source: https://developer.repebble.com/docs/c/User_Interface/Clicks/ Handling button click interactions Each Pebble window handles Pebble's buttons while it is displayed. Raw button down and button up events are transformed into click events that can be transferred to your app: - Single-click. Detects a single click, that is, a button down event followed by a button up event. It also offers hold-to-repeat functionality (repeated click). - Multi-click. Detects double-clicking, triple-clicking and other arbitrary click counts. It can fire its event handler on all of the matched clicks, or just the last. - Long-click. Detects long clicks, that is, press-and-hold. - Raw. Simply forwards the raw button events. It is provided as a way to use both the higher level "clicks" processing and the raw button events at the same time. To receive click events when a window is displayed, you must register a [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) for this window with [window\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider). Your [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) will be called every time the window becomes visible with one context argument. By default this context is a pointer to the window but you can change this with [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context). In your [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) you call the [window\_single\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe), [window\_single\_repeating\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_repeating_click_subscribe), [window\_multi\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_multi_click_subscribe), [window\_long\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_long_click_subscribe) and [window\_raw\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_raw_click_subscribe) functions to register a handler for each event you wish to receive. For convenience, click handlers are provided with a [ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) and a user-specified context. The [ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) can be used in combination with [click\_number\_of\_clicks\_counted()](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_number_of_clicks_counted), [click\_recognizer\_get\_button\_id()](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_recognizer_get_button_id) and [click\_recognizer\_is\_repeating()](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_recognizer_is_repeating) to get more information about the click. This is useful if you want different buttons or event types to share the same handler. The user-specified context is the context of your [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) (see above). By default it points to the window. You can override it for all handlers with [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) or for a specific button with [window\_set\_click\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_context). ### User interaction in watchfaces Watchfaces cannot use the buttons to interact with the user. Instead, you can use the [AccelerometerService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md). ### About the Back button By default, the Back button will always pop to the previous window on the [Window Stack](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md) (and leave the app if the current window is the only window). You can override the default back button behavior with [window\_single\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe) and [window\_multi\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_multi_click_subscribe) but you cannot set a repeating, long or raw click handler on the back button because a long press will always terminate the app and return to the main menu. ### Usage example First associate a click config provider callback with your window: ``` void app\_init(void) {...window\_set\_click\_config\_provider(&window, (ClickConfigProvider) config\_provider);...} ``` Then in the callback, you set your desired configuration for each button: ``` void config\_provider(Window \*window) { // single click / repeat-on-hold config:window\_single\_click\_subscribe(BUTTON\_ID\_DOWN, down\_single\_click\_handler);window\_single\_repeating\_click\_subscribe(BUTTON\_ID\_SELECT, 1000, select\_single\_click\_handler);// multi click config:window\_multi\_click\_subscribe(BUTTON\_ID\_SELECT, 2, 10, 0, true, select\_multi\_click\_handler);// long click config:window\_long\_click\_subscribe(BUTTON\_ID\_SELECT, 700, select\_long\_click\_handler, select\_long\_click\_release\_handler);} ``` Now you implement the handlers for each click you've subscribed to and set up: ``` void down\_single\_click\_handler(ClickRecognizerRef recognizer, void \*context) {... called on single click ...Window \*window = (Window \*)context;}void select\_single\_click\_handler(ClickRecognizerRef recognizer, void \*context) {... called on single click, and every 1000ms of being held ...Window \*window = (Window \*)context;}void select\_multi\_click\_handler(ClickRecognizerRef recognizer, void \*context) {... called for multi-clicks ...Window \*window = (Window \*)context;const uint16\_t count = click\_number\_of\_clicks\_counted(recognizer);}void select\_long\_click\_handler(ClickRecognizerRef recognizer, void \*context) {... called on long click start ...Window \*window = (Window \*)context;}void select\_long\_click\_release\_handler(ClickRecognizerRef recognizer, void \*context) {... called when long click is released ...Window \*window = (Window \*)context;} ``` ### See also Refer to the [User Interface Layers chapter in the Pebble Developer Guides](https://developer.repebble.com/guides/user-interfaces/layers.md) (chapter "Clicks") for a conceptual overview of clicks and relevant code examples. ## Function Documentation uint8\_t click\_number\_of\_clicks\_counted([ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) recognizer) Gets the click count. You can use this inside a click handler implementation to get the click count for multi\_click and (repeated) click events. #### Parameters recognizer The click recognizer for which to get the click count #### Returns The number of consecutive clicks, and for auto-repeating the number of repetitions. [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) click\_recognizer\_get\_button\_id([ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) recognizer) Gets the button identifier. You can use this inside a click handler implementation to get the button id for the click event. #### Parameters recognizer The click recognizer for which to get the button id that caused the click event #### Returns the [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) of the click recognizer bool click\_recognizer\_is\_repeating([ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) recognizer) Is this a repeating click. You can use this inside a click handler implementation to find out whether this is a repeating click or not. #### Parameters recognizer The click recognizer for which to find out whether this is a repeating click. #### Returns true if this is a repeating click. ## Enum Documentation enum ButtonId Button ID values. click\_recognizer\_get\_button\_id() #### Enumerators BUTTON\_ID\_BACK Back button. BUTTON\_ID\_UP Up button. BUTTON\_ID\_SELECT Select (middle) button. BUTTON\_ID\_DOWN Down button. NUM\_BUTTONS Total number of buttons. ## Typedef Documentation typedef void \* ClickRecognizerRef Reference to opaque click recognizer When a [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) callback is called, the recognizer that fired the handler is passed in. #### See Also [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) [click\_number\_of\_clicks\_counted()](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_number_of_clicks_counted) [click\_recognizer\_get\_button\_id()](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_recognizer_get_button_id) [click\_recognizer\_is\_repeating()](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_recognizer_is_repeating) typedef void(\* ClickHandler)(ClickRecognizerRef recognizer, void \*context) Function signature of the callback that handles a recognized click pattern. #### Parameters recognizer The click recognizer that detected a "click" pattern context Pointer to application specified data (see [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) and [window\_set\_click\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_context)). This defaults to the window. #### See Also [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) typedef void(\* ClickConfigProvider)(void \*context) This callback is called every time the window becomes visible (and when you call [window\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider) if the window is already visible). Subscribe to click events using [window\_single\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe) [window\_single\_repeating\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_repeating_click_subscribe) [window\_multi\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_multi_click_subscribe) [window\_long\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_long_click_subscribe) [window\_raw\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_raw_click_subscribe) These subscriptions will get used by the click recognizers of each of the 4 buttons. #### Parameters context Pointer to application specific data (see [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context)). --- # Color Definitions Source: https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions/ A list of all of the named colors available with links to the color map on the Pebble Developer website. ## Macro Definition Documentation #define GColorBlackARGB8 ((uint8\_t)0b11000000) #define GColorOxfordBlueARGB8 ((uint8\_t)0b11000001) #define GColorDukeBlueARGB8 ((uint8\_t)0b11000010) #define GColorBlueARGB8 ((uint8\_t)0b11000011) #define GColorDarkGreenARGB8 ((uint8\_t)0b11000100) #define GColorMidnightGreenARGB8 ((uint8\_t)0b11000101) #define GColorCobaltBlueARGB8 ((uint8\_t)0b11000110) #define GColorBlueMoonARGB8 ((uint8\_t)0b11000111) #define GColorIslamicGreenARGB8 ((uint8\_t)0b11001000) #define GColorJaegerGreenARGB8 ((uint8\_t)0b11001001) #define GColorTiffanyBlueARGB8 ((uint8\_t)0b11001010) #define GColorVividCeruleanARGB8 ((uint8\_t)0b11001011) #define GColorGreenARGB8 ((uint8\_t)0b11001100) #define GColorMalachiteARGB8 ((uint8\_t)0b11001101) #define GColorMediumSpringGreenARGB8 ((uint8\_t)0b11001110) #define GColorCyanARGB8 ((uint8\_t)0b11001111) #define GColorBulgarianRoseARGB8 ((uint8\_t)0b11010000) #define GColorImperialPurpleARGB8 ((uint8\_t)0b11010001) #define GColorIndigoARGB8 ((uint8\_t)0b11010010) #define GColorElectricUltramarineARGB8 ((uint8\_t)0b11010011) #define GColorArmyGreenARGB8 ((uint8\_t)0b11010100) #define GColorDarkGrayARGB8 ((uint8\_t)0b11010101) #define GColorLibertyARGB8 ((uint8\_t)0b11010110) #define GColorVeryLightBlueARGB8 ((uint8\_t)0b11010111) #define GColorKellyGreenARGB8 ((uint8\_t)0b11011000) #define GColorMayGreenARGB8 ((uint8\_t)0b11011001) #define GColorCadetBlueARGB8 ((uint8\_t)0b11011010) #define GColorPictonBlueARGB8 ((uint8\_t)0b11011011) #define GColorBrightGreenARGB8 ((uint8\_t)0b11011100) #define GColorScreaminGreenARGB8 ((uint8\_t)0b11011101) #define GColorMediumAquamarineARGB8 ((uint8\_t)0b11011110) #define GColorElectricBlueARGB8 ((uint8\_t)0b11011111) #define GColorDarkCandyAppleRedARGB8 ((uint8\_t)0b11100000) #define GColorJazzberryJamARGB8 ((uint8\_t)0b11100001) #define GColorPurpleARGB8 ((uint8\_t)0b11100010) #define GColorVividVioletARGB8 ((uint8\_t)0b11100011) #define GColorWindsorTanARGB8 ((uint8\_t)0b11100100) #define GColorRoseValeARGB8 ((uint8\_t)0b11100101) #define GColorPurpureusARGB8 ((uint8\_t)0b11100110) #define GColorLavenderIndigoARGB8 ((uint8\_t)0b11100111) #define GColorLimerickARGB8 ((uint8\_t)0b11101000) #define GColorBrassARGB8 ((uint8\_t)0b11101001) #define GColorLightGrayARGB8 ((uint8\_t)0b11101010) #define GColorBabyBlueEyesARGB8 ((uint8\_t)0b11101011) #define GColorSpringBudARGB8 ((uint8\_t)0b11101100) #define GColorInchwormARGB8 ((uint8\_t)0b11101101) #define GColorMintGreenARGB8 ((uint8\_t)0b11101110) #define GColorCelesteARGB8 ((uint8\_t)0b11101111) #define GColorRedARGB8 ((uint8\_t)0b11110000) #define GColorFollyARGB8 ((uint8\_t)0b11110001) #define GColorFashionMagentaARGB8 ((uint8\_t)0b11110010) #define GColorMagentaARGB8 ((uint8\_t)0b11110011) #define GColorOrangeARGB8 ((uint8\_t)0b11110100) #define GColorSunsetOrangeARGB8 ((uint8\_t)0b11110101) #define GColorBrilliantRoseARGB8 ((uint8\_t)0b11110110) #define GColorShockingPinkARGB8 ((uint8\_t)0b11110111) #define GColorChromeYellowARGB8 ((uint8\_t)0b11111000) #define GColorRajahARGB8 ((uint8\_t)0b11111001) #define GColorMelonARGB8 ((uint8\_t)0b11111010) #define GColorRichBrilliantLavenderARGB8 ((uint8\_t)0b11111011) #define GColorYellowARGB8 ((uint8\_t)0b11111100) #define GColorIcterineARGB8 ((uint8\_t)0b11111101) #define GColorPastelYellowARGB8 ((uint8\_t)0b11111110) #define GColorWhiteARGB8 ((uint8\_t)0b11111111) #define GColorBlack (GColor8){.argb=GColorBlackARGB8} [GColorBlack](https://developer.repebble.com/tools/color-picker/#000000) #define GColorOxfordBlue (GColor8){.argb=GColorOxfordBlueARGB8} [GColorOxfordBlue](https://developer.repebble.com/tools/color-picker/#000055) #define GColorDukeBlue (GColor8){.argb=GColorDukeBlueARGB8} [GColorDukeBlue](https://developer.repebble.com/tools/color-picker/#0000AA) #define GColorBlue (GColor8){.argb=GColorBlueARGB8} [GColorBlue](https://developer.repebble.com/tools/color-picker/#0000FF) #define GColorDarkGreen (GColor8){.argb=GColorDarkGreenARGB8} [GColorDarkGreen](https://developer.repebble.com/tools/color-picker/#005500) #define GColorMidnightGreen (GColor8){.argb=GColorMidnightGreenARGB8} [GColorMidnightGreen](https://developer.repebble.com/tools/color-picker/#005555) #define GColorCobaltBlue (GColor8){.argb=GColorCobaltBlueARGB8} [GColorCobaltBlue](https://developer.repebble.com/tools/color-picker/#0055AA) #define GColorBlueMoon (GColor8){.argb=GColorBlueMoonARGB8} [GColorBlueMoon](https://developer.repebble.com/tools/color-picker/#0055FF) #define GColorIslamicGreen (GColor8){.argb=GColorIslamicGreenARGB8} [GColorIslamicGreen](https://developer.repebble.com/tools/color-picker/#00AA00) #define GColorJaegerGreen (GColor8){.argb=GColorJaegerGreenARGB8} [GColorJaegerGreen](https://developer.repebble.com/tools/color-picker/#00AA55) #define GColorTiffanyBlue (GColor8){.argb=GColorTiffanyBlueARGB8} [GColorTiffanyBlue](https://developer.repebble.com/tools/color-picker/#00AAAA) #define GColorVividCerulean (GColor8){.argb=GColorVividCeruleanARGB8} [GColorVividCerulean](https://developer.repebble.com/tools/color-picker/#00AAFF) #define GColorGreen (GColor8){.argb=GColorGreenARGB8} [GColorGreen](https://developer.repebble.com/tools/color-picker/#00FF00) #define GColorMalachite (GColor8){.argb=GColorMalachiteARGB8} [GColorMalachite](https://developer.repebble.com/tools/color-picker/#00FF55) #define GColorMediumSpringGreen (GColor8){.argb=GColorMediumSpringGreenARGB8} [GColorMediumSpringGreen](https://developer.repebble.com/tools/color-picker/#00FFAA) #define GColorCyan (GColor8){.argb=GColorCyanARGB8} [GColorCyan](https://developer.repebble.com/tools/color-picker/#00FFFF) #define GColorBulgarianRose (GColor8){.argb=GColorBulgarianRoseARGB8} [GColorBulgarianRose](https://developer.repebble.com/tools/color-picker/#550000) #define GColorImperialPurple (GColor8){.argb=GColorImperialPurpleARGB8} [GColorImperialPurple](https://developer.repebble.com/tools/color-picker/#550055) #define GColorIndigo (GColor8){.argb=GColorIndigoARGB8} [GColorIndigo](https://developer.repebble.com/tools/color-picker/#5500AA) #define GColorElectricUltramarine (GColor8){.argb=GColorElectricUltramarineARGB8} [GColorElectricUltramarine](https://developer.repebble.com/tools/color-picker/#5500FF) #define GColorArmyGreen (GColor8){.argb=GColorArmyGreenARGB8} [GColorArmyGreen](https://developer.repebble.com/tools/color-picker/#555500) #define GColorDarkGray (GColor8){.argb=GColorDarkGrayARGB8} [GColorDarkGray](https://developer.repebble.com/tools/color-picker/#555555) #define GColorLiberty (GColor8){.argb=GColorLibertyARGB8} [GColorLiberty](https://developer.repebble.com/tools/color-picker/#5555AA) #define GColorVeryLightBlue (GColor8){.argb=GColorVeryLightBlueARGB8} [GColorVeryLightBlue](https://developer.repebble.com/tools/color-picker/#5555FF) #define GColorKellyGreen (GColor8){.argb=GColorKellyGreenARGB8} [GColorKellyGreen](https://developer.repebble.com/tools/color-picker/#55AA00) #define GColorMayGreen (GColor8){.argb=GColorMayGreenARGB8} [GColorMayGreen](https://developer.repebble.com/tools/color-picker/#55AA55) #define GColorCadetBlue (GColor8){.argb=GColorCadetBlueARGB8} [GColorCadetBlue](https://developer.repebble.com/tools/color-picker/#55AAAA) #define GColorPictonBlue (GColor8){.argb=GColorPictonBlueARGB8} [GColorPictonBlue](https://developer.repebble.com/tools/color-picker/#55AAFF) #define GColorBrightGreen (GColor8){.argb=GColorBrightGreenARGB8} [GColorBrightGreen](https://developer.repebble.com/tools/color-picker/#55FF00) #define GColorScreaminGreen (GColor8){.argb=GColorScreaminGreenARGB8} [GColorScreaminGreen](https://developer.repebble.com/tools/color-picker/#55FF55) #define GColorMediumAquamarine (GColor8){.argb=GColorMediumAquamarineARGB8} [GColorMediumAquamarine](https://developer.repebble.com/tools/color-picker/#55FFAA) #define GColorElectricBlue (GColor8){.argb=GColorElectricBlueARGB8} [GColorElectricBlue](https://developer.repebble.com/tools/color-picker/#55FFFF) #define GColorDarkCandyAppleRed (GColor8){.argb=GColorDarkCandyAppleRedARGB8} [GColorDarkCandyAppleRed](https://developer.repebble.com/tools/color-picker/#AA0000) #define GColorJazzberryJam (GColor8){.argb=GColorJazzberryJamARGB8} [GColorJazzberryJam](https://developer.repebble.com/tools/color-picker/#AA0055) #define GColorPurple (GColor8){.argb=GColorPurpleARGB8} [GColorPurple](https://developer.repebble.com/tools/color-picker/#AA00AA) #define GColorVividViolet (GColor8){.argb=GColorVividVioletARGB8} [GColorVividViolet](https://developer.repebble.com/tools/color-picker/#AA00FF) #define GColorWindsorTan (GColor8){.argb=GColorWindsorTanARGB8} [GColorWindsorTan](https://developer.repebble.com/tools/color-picker/#AA5500) #define GColorRoseVale (GColor8){.argb=GColorRoseValeARGB8} [GColorRoseVale](https://developer.repebble.com/tools/color-picker/#AA5555) #define GColorPurpureus (GColor8){.argb=GColorPurpureusARGB8} [GColorPurpureus](https://developer.repebble.com/tools/color-picker/#AA55AA) #define GColorLavenderIndigo (GColor8){.argb=GColorLavenderIndigoARGB8} [GColorLavenderIndigo](https://developer.repebble.com/tools/color-picker/#AA55FF) #define GColorLimerick (GColor8){.argb=GColorLimerickARGB8} [GColorLimerick](https://developer.repebble.com/tools/color-picker/#AAAA00) #define GColorBrass (GColor8){.argb=GColorBrassARGB8} [GColorBrass](https://developer.repebble.com/tools/color-picker/#AAAA55) #define GColorLightGray (GColor8){.argb=GColorLightGrayARGB8} [GColorLightGray](https://developer.repebble.com/tools/color-picker/#AAAAAA) #define GColorBabyBlueEyes (GColor8){.argb=GColorBabyBlueEyesARGB8} [GColorBabyBlueEyes](https://developer.repebble.com/tools/color-picker/#AAAAFF) #define GColorSpringBud (GColor8){.argb=GColorSpringBudARGB8} [GColorSpringBud](https://developer.repebble.com/tools/color-picker/#AAFF00) #define GColorInchworm (GColor8){.argb=GColorInchwormARGB8} [GColorInchworm](https://developer.repebble.com/tools/color-picker/#AAFF55) #define GColorMintGreen (GColor8){.argb=GColorMintGreenARGB8} [GColorMintGreen](https://developer.repebble.com/tools/color-picker/#AAFFAA) #define GColorCeleste (GColor8){.argb=GColorCelesteARGB8} [GColorCeleste](https://developer.repebble.com/tools/color-picker/#AAFFFF) #define GColorRed (GColor8){.argb=GColorRedARGB8} [GColorRed](https://developer.repebble.com/tools/color-picker/#FF0000) #define GColorFolly (GColor8){.argb=GColorFollyARGB8} [GColorFolly](https://developer.repebble.com/tools/color-picker/#FF0055) #define GColorFashionMagenta (GColor8){.argb=GColorFashionMagentaARGB8} [GColorFashionMagenta](https://developer.repebble.com/tools/color-picker/#FF00AA) #define GColorMagenta (GColor8){.argb=GColorMagentaARGB8} [GColorMagenta](https://developer.repebble.com/tools/color-picker/#FF00FF) #define GColorOrange (GColor8){.argb=GColorOrangeARGB8} [GColorOrange](https://developer.repebble.com/tools/color-picker/#FF5500) #define GColorSunsetOrange (GColor8){.argb=GColorSunsetOrangeARGB8} [GColorSunsetOrange](https://developer.repebble.com/tools/color-picker/#FF5555) #define GColorBrilliantRose (GColor8){.argb=GColorBrilliantRoseARGB8} [GColorBrilliantRose](https://developer.repebble.com/tools/color-picker/#FF55AA) #define GColorShockingPink (GColor8){.argb=GColorShockingPinkARGB8} [GColorShockingPink](https://developer.repebble.com/tools/color-picker/#FF55FF) #define GColorChromeYellow (GColor8){.argb=GColorChromeYellowARGB8} [GColorChromeYellow](https://developer.repebble.com/tools/color-picker/#FFAA00) #define GColorRajah (GColor8){.argb=GColorRajahARGB8} [GColorRajah](https://developer.repebble.com/tools/color-picker/#FFAA55) #define GColorMelon (GColor8){.argb=GColorMelonARGB8} [GColorMelon](https://developer.repebble.com/tools/color-picker/#FFAAAA) #define GColorRichBrilliantLavender (GColor8){.argb=GColorRichBrilliantLavenderARGB8} [GColorRichBrilliantLavender](https://developer.repebble.com/tools/color-picker/#FFAAFF) #define GColorYellow (GColor8){.argb=GColorYellowARGB8} [GColorYellow](https://developer.repebble.com/tools/color-picker/#FFFF00) #define GColorIcterine (GColor8){.argb=GColorIcterineARGB8} [GColorIcterine](https://developer.repebble.com/tools/color-picker/#FFFF55) #define GColorPastelYellow (GColor8){.argb=GColorPastelYellowARGB8} [GColorPastelYellow](https://developer.repebble.com/tools/color-picker/#FFFFAA) #define GColorWhite (GColor8){.argb=GColorWhiteARGB8} [GColorWhite](https://developer.repebble.com/tools/color-picker/#FFFFFF) #define GColorClearARGB8 ((uint8\_t)0b00000000) #define GColorClear ((GColor8){.argb=GColorClearARGB8}) --- # CompassService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService/ The Compass Service combines information from Pebble's accelerometer and magnetometer to automatically calibrate the compass and transform the raw magnetic field information into a [CompassHeading](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeading), that is an angle to north. It also provides magnetic north and information about its status and accuracy through the [CompassHeadingData](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData) structure. The API is designed to also provide true north in a future release. Note that not all platforms have compasses. To check for the presence of a compass at compile time for the current platform use the `PBL_COMPASS` define. To learn more about the Compass Service and how to use it, read the [Determining Direction](https://developer.repebble.com/guides/pebble-apps/sensors/magnetometer/) guide. For available code samples, see the [feature-compass](https://github.com/pebble-examples/feature-compass) example. ## Function Documentation int compass\_service\_set\_heading\_filter([CompassHeading](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeading) filter) Set the minimum angular change required to generate new compass heading events. The angular distance is measured relative to the last delivered heading event. Use 0 to be notified of all movements. Negative values and values \> TRIG\_MAX\_ANGLE / 2 are not valid. The default value of this property is TRIG\_MAX\_ANGLE / 360. #### Returns 0, success. Non-Zero, if filter is invalid. #### See Also [compass\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_subscribe) void compass\_service\_subscribe([CompassHeadingHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingHandler) handler) Subscribe to the compass heading event service. Once subscribed, the handler gets called every time the angular distance relative to the previous value exceeds the configured filter. #### Parameters handler A callback to be executed on heading events #### See Also [compass\_service\_set\_heading\_filter](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_set_heading_filter) [compass\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_unsubscribe) void compass\_service\_unsubscribe(void) Unsubscribe from the compass heading event service. Once unsubscribed, the previously registered handler will no longer be called. #### See Also [compass\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_subscribe) int compass\_service\_peek([CompassHeadingData](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData) \* data) Peek at the last recorded reading. #### Parameters data (out) a pointer to a pre-allocated [CompassHeadingData](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData) #### Returns Always returns 0 to indicate success. ## Data Structure Documentation struct CompassHeadingData Structure containing a single heading towards magnetic and true north. #### Data Fields [CompassHeading](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeading) magnetic\_heading Measured angle that increases counter-clockwise from magnetic north (use `int clockwise_heading = TRIG_MAX_ANGLE - heading_data.magnetic_heading;` for example to find your heading clockwise from magnetic north). [CompassHeading](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeading) true\_heading Currently same value as magnetic\_heading (reserved for future implementation). [CompassStatus](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatus) compass\_status Indicates the current state of the Compass Service calibration. bool is\_declination\_valid Currently always false (reserved for future implementation). ## Enum Documentation enum CompassStatus Enum describing the current state of the Compass Service. #### Enumerators CompassStatusUnavailable The Compass Service is unavailable. CompassStatusDataInvalid Compass is calibrating: data is invalid and should not be used Data will become valid once calibration is complete. CompassStatusCalibrating Compass is calibrating: the data is valid but the calibration is still being refined. CompassStatusCalibrated Compass data is valid and the calibration has completed. ## Typedef Documentation typedef int32\_t CompassHeading Represents an angle relative to get to a reference direction, e.g. (magnetic) north. The angle value is scaled linearly, such that a value of TRIG\_MAX\_ANGLE corresponds to 360 degrees or 2 PI radians. Thus, if heading towards north, north is 0, west is TRIG\_MAX\_ANGLE/4, south is TRIG\_MAX\_ANGLE/2, and so on. typedef void(\* CompassHeadingHandler)(CompassHeadingData heading) Callback type for compass heading events. #### Parameters heading copy of last recorded heading --- # ConnectionService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService/ Determine what the Pebble watch is connected to The ConnectionService allows your app to learn about the apps the Pebble watch is connected to. You can ask the system for this information at a given time or you can register to receive events every time connection or disconnection events occur. It allows you to determine whether the watch is connected to the Pebble mobile app by subscribing to the pebble\_app\_connection\_handler or by calling the connection\_service\_peek\_pebble\_app\_connection function. Note that when the Pebble app is connected, you can assume PebbleKit JS apps will also be running correctly. The service also allows you to determine if the Pebble watch can establish a connection to a PebbleKit companion app by subscribing to the pebblekit\_connection\_handler or by calling the connection\_service\_peek\_pebblekit\_connection function. Today, due to architectural differences between iOS and Android, this will return true for Android anytime a connection with the Pebble mobile app is established (since PebbleKit messages are routed through the Android app). For iOS, this will return true when any PebbleKit companion app has established a connection with the Pebble watch (since companion app messages are routed directly to the watch) ## Function Documentation bool connection\_service\_peek\_pebble\_app\_connection(void) Query the bluetooth connection service for the current Pebble app connection status. #### Returns true if the Pebble app is connected, false otherwise bool connection\_service\_peek\_pebblekit\_connection(void) Query the bluetooth connection service for the current PebbleKit connection status. #### Returns true if a PebbleKit companion app is connected, false otherwise - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void connection\_service\_subscribe(ConnectionHandlers conn\_handlers) Subscribe to the connection event service. Once subscribed, the appropriate handler gets called based on the type of connection event and user provided handlers. #### Parameters ConnectionHandlers A struct populated with the handlers to be called when the specified connection event occurs. If a given handler is NULL, no function will be called. void connection\_service\_subscribe(ConnectionHandlers conn\_handlers) Subscribe to the connection event service. Once subscribed, the appropriate handler gets called based on the type of connection event and user provided handlers. #### Parameters ConnectionHandlers A struct populated with the handlers to be called when the specified connection event occurs. If a given handler is NULL, no function will be called. void connection\_service\_subscribe(ConnectionHandlers conn\_handlers) Subscribe to the connection event service. Once subscribed, the appropriate handler gets called based on the type of connection event and user provided handlers. #### Parameters conn\_handlers A struct populated with the handlers to be called when the specified connection event occurs. If a given handler is NULL, no function will be called. void connection\_service\_unsubscribe(void) Unsubscribe from the bluetooth event service. Once unsubscribed, the previously registered handler will no longer be called. bool bluetooth\_connection\_service\_peek(void) DeprecatedBackward compatibility function for connection\_service\_peek\_pebble\_app\_connection. New code should use connection\_service\_peek\_pebble\_app\_connection directly. This will be removed in a future version of the Pebble SDK void bluetooth\_connection\_service\_subscribe(ConnectionHandler handler) DeprecatedBackward compatibility function for connection\_service\_subscribe. New code should use connection\_service\_subscribe directly. This will be removed in a future version of the Pebble SDK void bluetooth\_connection\_service\_unsubscribe(void) DeprecatedBackward compatibility function for connection\_service\_unsubscribe. New code should use connection\_service\_unsubscribe directly. This will be removed in a future version of the Pebble SDK ## Data Structure Documentation struct ConnectionHandlers #### Data Fields ConnectionHandler pebble\_app\_connection\_handler callback to be executed when the connection state between the watch and the phone app has changed. Note, if the phone App is connected, PebbleKit JS apps will also be working correctly ConnectionHandler pebblekit\_connection\_handler ID for callback to be executed on PebbleKit connection event. ## Typedef Documentation typedef void(\* ConnectionHandler)(bool connected) typedef ConnectionHandler BluetoothConnectionHandler DeprecatedBackwards compatibility typedef for ConnectionHandler. New code should use ConnectionHandler directly. This will be removed in a future version of the Pebble SDK. --- # DataLogging Source: https://developer.repebble.com/docs/c/Foundation/DataLogging/ Enables logging data asynchronously to a mobile app In Pebble OS, data logging is a data storage and transfer subsystem that allows watchapps to save data on non-volatile storage devices when the phone is not available to process it. The API provides your watchapp with a mechanism for short-term data buffering for asynchronous data transmission to a mobile app. Using this API, your Pebble watchapp can create an arbitrary number of logs, but you’re limited in the amount of storage space you can use. Note that approximately 640K is available for data logging, which is shared among all watchapps that use it. This value is subject to change in the future. When the data spool is full, an app will start overwriting its own data. An app cannot overwrite another apps's data. However, the other app might have 0 bytes for data logging. Your app can log data to a session. Every new block of data is appended to the session. The data is then sent to the associated phone application at the earliest convenience. If a phone is available, the data is sent directly to the phone. Otherwise, it is saved to the watch storage until the watch is connected to a phone. For example: To create a data logging session for 4-byte unsigned integers with a tag of 0x1234, you would do this: ``` DataLoggingSessionRef logging\_session = data\_logging\_create(0x1234, DATA\_LOGGING\_UINT, 4,false);// Fake creating some data and logging it to the session.uint32\_t data[] = { 1, 2, 3};data\_logging\_log(logging\_session, &data, 3);// Fake creating more data and logging that as well.uint32\_t data2[] = { 1, 2 };data\_logging\_log(logging\_session, &data, 2);// When we don't need to log anything else, we can close off the session.data\_logging\_finish(logging\_session); ``` ## Function Documentation DataLoggingSessionRef data\_logging\_create([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) tag, [DataLoggingItemType](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingItemType) item\_type, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) item\_length, bool resume) Create a new data logging session. #### Parameters tag A tag associated with the logging session. item\_type The type of data stored in this logging session item\_length The size of a single data item in bytes resume True if we want to look for a logging session of the same tag and resume logging to it. If this is false and a session with the specified tag exists, that session will be closed and a new session will be opened. #### Returns An opaque reference to the data logging session void data\_logging\_finish(DataLoggingSessionRef logging\_session) Finish up a data logging\_session. Logging data is kept until it has successfully been transferred over to the phone, but no data may be added to the session after this function is called. #### Parameters logging\_session a reference to the data logging session previously allocated using data\_logging\_create [DataLoggingResult](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingResult) data\_logging\_log(DataLoggingSessionRef logging\_session, const void \* data, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_items) Add data to the data logging session. If a phone is available, the data is sent directly to the phone. Otherwise, it is saved to the watch storage until the watch is connected to a phone. #### Parameters logging\_session a reference to the data logging session you want to add the data to data a pointer to the data buffer that contains multiple items num\_items the number of items to log. This means data must be at least (num\_items \* item\_length) long in bytes #### Returns DATA\_LOGGING\_SUCCESS on success DATA\_LOGGING\_NOT\_FOUND if the logging session is invalid DATA\_LOGGING\_CLOSED if the sesion is not active DATA\_LOGGING\_BUSY if the sesion is not available for writing DATA\_LOGGING\_INVALID\_PARAMS if num\_items is 0 or data is NULL ## Enum Documentation enum DataLoggingItemType The different types of session data that Pebble supports. This type describes the type of a singular item in the data session. Every item in a given session is the same type and size. #### Enumerators DATA\_LOGGING\_BYTE\_ARRAY Array of bytes. Remember that this is the type of a single item in the logging session, so using this type means you'll be logging multiple byte arrays (each a fixed length described by item\_length) for the duration of the session. DATA\_LOGGING\_UINT Unsigned integer. This may be a 1, 2, or 4 byte integer depending on the item\_length parameter. DATA\_LOGGING\_INT Signed integer. This may be a 1, 2, or 4 byte integer depending on the item\_length parameter. enum DataLoggingResult Enumerated values describing the possible outcomes of data logging operations. #### Enumerators DATA\_LOGGING\_SUCCESS Successful operation. DATA\_LOGGING\_BUSY Someone else is writing to this logging session. DATA\_LOGGING\_FULL No more space to save data. DATA\_LOGGING\_NOT\_FOUND The logging session does not exist. DATA\_LOGGING\_CLOSED The logging session was made inactive. DATA\_LOGGING\_INVALID\_PARAMS An invalid parameter was passed to one of the functions. DATA\_LOGGING\_INTERNAL\_ERR An internal error occurred. ## Typedef Documentation typedef void \* DataLoggingSessionRef --- # Dictation Source: https://developer.repebble.com/docs/c/Foundation/Dictation/ ## Function Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) DictationSession \* dictation\_session\_create([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) buffer\_size, [DictationSessionStatusCallback](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback) callback, void \* callback\_context) Create a dictation session. The session object can be used more than once to get a transcription. When a transcription is received a buffer will be allocated to store the text in with a maximum size specified by buffer\_size. When a transcription and accepted by the user or a failure of some sort occurs, the callback specified will be called with the status and the transcription if one was accepted. #### Parameters buffer\_size size of buffer to allocate for the transcription text; text will be truncated if it is longer than the maximum size specified; a size of 0 will allow the session to allocate as much as it needs and text will not be truncated callback dictation session status handler (must be valid) callback\_context context pointer for status handler #### Returns handle to the dictation session or NULL if the phone app is not connected or does not support voice dictation, if this is called on a platform that doesn't support voice dictation, or if an internal error occurs. DictationSession \* dictation\_session\_create([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) buffer\_size, [DictationSessionStatusCallback](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback) callback, void \* callback\_context) Create a dictation session. The session object can be used more than once to get a transcription. When a transcription is received a buffer will be allocated to store the text in with a maximum size specified by buffer\_size. When a transcription and accepted by the user or a failure of some sort occurs, the callback specified will be called with the status and the transcription if one was accepted. #### Parameters buffer\_size size of buffer to allocate for the transcription text; text will be truncated if it is longer than the maximum size specified; a size of 0 will allow the session to allocate as much as it needs and text will not be truncated callback dictation session status handler (must be valid) callback\_context context pointer for status handler #### Returns handle to the dictation session or NULL if the phone app is not connected or does not support voice dictation, if this is called on a platform that doesn't support voice dictation, or if an internal error occurs. DictationSession \* dictation\_session\_create([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) buffer\_size, [DictationSessionStatusCallback](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback) callback, void \* callback\_context) Create a dictation session. The session object can be used more than once to get a transcription. When a transcription is received a buffer will be allocated to store the text in with a maximum size specified by `buffer_size`. When a transcription is accepted by the user or a failure of some sort occurs, the callback specified will be called with the status and the transcription if one was accepted. #### Parameters buffer\_size size of buffer to allocate for the transcription text; text will be truncated if it is longer than the maximum size specified; a size of 0 will allow the session to allocate as much as it needs and text will not be truncated callback dictation session status handler (must be valid) callback\_context context pointer for status handler #### Returns handle to the dictation session or NULL if the phone app is not connected or does not support voice dictation, if this is called on a platform that doesn't support voice dictation, or if an internal error occurs. void dictation\_session\_destroy(DictationSession \* session) Destroy the dictation session and free its memory. Will terminate a session in progress. #### Parameters session dictation session to be destroyed [DictationSessionStatus](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatus) dictation\_session\_start(DictationSession \* session) Start the dictation session. The dictation UI will be shown. When the user accepts a transcription or exits the UI, or, when the confirmation dialog is disabled and a status is received, the status callback will be called. Can only be called when no session is in progress. The session can be restarted multiple times after the UI is exited or the session is stopped. #### Parameters session dictation session to start or restart #### Returns true if session was started, false if session is already in progress or is invalid. [DictationSessionStatus](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatus) dictation\_session\_stop(DictationSession \* session) Stop the current dictation session. The UI will be hidden and no status callbacks will be received after the session is stopped. #### Parameters session dictation session to stop #### Returns true if session was stopped, false if session was not started or is invalid void dictation\_session\_enable\_confirmation(DictationSession \* session, bool is\_enabled) Enable or disable user confirmation of transcribed text, which allows the user to accept or reject (and restart) the transcription. Must be called before the session is started. #### Parameters session dictation session to modify is\_enabled set to true to enable user confirmation of transcriptions (default), false to disable void dictation\_session\_enable\_error\_dialogs(DictationSession \* session, bool is\_enabled) Enable or disable error dialogs when transcription fails. Must be called before the session is started. Disabling error dialogs will also disable automatic retries if transcription fails. #### Parameters session dictation session to modify is\_enabled set to true to enable error dialogs (default), false to disable ## Enum Documentation enum DictationSessionStatus #### Enumerators DictationSessionStatusSuccess Transcription successful, with a valid result. DictationSessionStatusFailureTranscriptionRejected User rejected transcription and exited UI. DictationSessionStatusFailureTranscriptionRejectedWithError User exited UI after transcription error. DictationSessionStatusFailureSystemAborted Too many errors occurred during transcription and the UI exited. DictationSessionStatusFailureNoSpeechDetected No speech was detected and UI exited. DictationSessionStatusFailureConnectivityError No BT or internet connection. DictationSessionStatusFailureDisabled Voice transcription disabled for this user. DictationSessionStatusFailureInternalError Voice transcription failed due to internal error. DictationSessionStatusFailureRecognizerError Cloud recognizer failed to transcribe speech (only possible if error dialogs disabled) ## Typedef Documentation typedef struct DictationSession DictationSession typedef void(\* DictationSessionStatusCallback)(DictationSession \*session, DictationSessionStatus status, char \*transcription, void \*context) Dictation status callback. Indicates success or failure of the dictation session and, if successful, passes the transcribed string to the user of the dictation session. The transcribed string will be freed after this call returns, so the string should be copied if it needs to be retained afterwards. #### Parameters session dictation session from which the status was received status dictation status transcription transcribed string context callback context specified when starting the session ## Macro Definition Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define PBL\_IF\_MICROPHONE\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expressions depending on mic support. On platforms with a mic the first expression will be chosen, the second otherwise. #define PBL\_IF\_MICROPHONE\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expressions depending on mic support. On platforms with a mic the first expression will be chosen, the second otherwise. #define PBL\_IF\_MICROPHONE\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expressions depending on mic support. On platforms with a mic the first expression will be chosen, the second otherwise. --- # Dictionary Source: https://developer.repebble.com/docs/c/Foundation/Dictionary/ Data serialization utilities Data residing in different parts of Pebble memory (RAM) may need to be gathered and assembled into a single continuous block for transport over the network via Bluetooth. The process of gathering and assembling this continuous block of data is called serialization. You use data serialization utilities, like Dictionary, [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) and [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) data structures and accompanying functions, to accomplish this task. No transformations are performed on the actual data, however. These Pebble utilities simply help assemble the data into one continuous buffer according to a specific format. [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md) uses these utilities-in particular, Dictionary-to send information between mobile and Pebble watchapps. ### Writing key/value pairs To write two key/value pairs, without using Tuplets, you would do this: ``` // Byte array + key:static const uint32\_t SOME\_DATA\_KEY = 0xb00bf00b;static const uint8\_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};// CString + key:static const uint32\_t SOME\_STRING\_KEY = 0xabbababe;static const char \*string = "Hello World";// Calculate the buffer size that is needed for the final Dictionary:const uint8\_t key\_count = 2;const uint32\_t size = dict\_calc\_buffer\_size(key\_count, sizeof(data),strlen(string) + 1);// Stack-allocated buffer in which to create the Dictionary:uint8\_t buffer[size];// Iterator variable, keeps the state of the creation serialization process:DictionaryIterator iter;// Begin:dict\_write\_begin(&iter, buffer, sizeof(buffer));// Write the Data:dict\_write\_data(&iter, SOME\_DATA\_KEY, data, sizeof(data));// Write the CString:dict\_write\_cstring(&iter, SOME\_STRING\_KEY, string);// End:const uint32\_t final\_size = dict\_write\_end(&iter);// buffer now contains the serialized information ``` ### Reading key/value pairs To iterate over the key/value pairs in the dictionary that was created in the previous example code, you would do this: ``` Tuple \*tuple = dict\_read\_begin\_from\_buffer(&iter, buffer, final\_size);while (tuple) {switch (tuple-\>key) {case SOME\_DATA\_KEY:foo(tuple-\>value-\>data, tuple-\>length);break;case SOME\_STRING\_KEY:bar(tuple-\>value-\>cstring);break;}tuple = dict\_read\_next(&iter);} ``` ### Tuple and Tuplet data structures To understand the difference between [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) and [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) data structures: [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) is the header for a serialized key/value pair, while [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) is a helper data structure that references the value you want to serialize. This data structure exists to make the creation of a Dictionary easier to write. Use this mnemonic to remember the difference: TupleT(emplate), the [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) being a template to create a Dictionary with [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) structures. For example: ``` Tuplet pairs[] = {TupletInteger(WEATHER\_ICON\_KEY, (uint8\_t) 1),TupletCString(WEATHER\_TEMPERATURE\_KEY, "1234 Fahrenheit"),};uint8\_t buffer[256];uint32\_t size = sizeof(buffer);dict\_serialize\_tuplets\_to\_buffer(pairs, ARRAY\_LENGTH(pairs), buffer, &size);// buffer now contains the serialized information ``` ## Function Documentation [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) dict\_calc\_buffer\_size(const uint8\_t tuple\_count, ...) Calculates the number of bytes that a dictionary will occupy, given one or more value lengths that need to be stored in the dictionary. ##### Note The formula to calculate the size of a Dictionary in bytes is: ``` 1 + (n * 7) + D1 + ... + Dn ``` Where `n` is the number of Tuples in the Dictionary and `Dx` are the sizes of the values in the Tuples. The size of the Dictionary header is 1 byte. The size of the header for each [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) is 7 bytes. #### Parameters tuple\_count The total number of key/value pairs in the dictionary. ... The sizes of each of the values that need to be stored in the dictionary. #### Returns The total number of bytes of storage needed. [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) dict\_size([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter) Calculates the size of data that has been written to the dictionary. AKA, the "dictionary size". Note that this is most likely different than the size of the backing storage/backing buffer. #### Parameters iter The dictionary iterator #### Returns The total number of bytes which have been written to the dictionary. [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_begin([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, uint8\_t \*const buffer, const [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) size) Initializes the dictionary iterator with a given buffer and size, resets and empties it, in preparation of writing key/value tuples. #### Parameters iter The dictionary iterator buffer The storage of the dictionary size The storage size of the dictionary #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS #### See Also [dict\_calc\_buffer\_size](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_calc_buffer_size) [dict\_write\_end](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_end) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_data([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const uint8\_t \*const data, const [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) size) Adds a key with a byte array value pair to the dictionary. ##### Notes The data will be copied into the backing storage of the dictionary. There is _no_ checking for duplicate keys. #### Parameters iter The dictionary iterator key The key data Pointer to the byte array size Length of the byte array #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_cstring([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const char \*const cstring) Adds a key with a C string value pair to the dictionary. ##### Notes The string will be copied into the backing storage of the dictionary. There is _no_ checking for duplicate keys. #### Parameters iter The dictionary iterator key The key cstring Pointer to the zero-terminated C string #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_int([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const void \* integer, const uint8\_t width\_bytes, const bool is\_signed) Adds a key with an integer value pair to the dictionary. ##### Note There is _no_ checking for duplicate keys. [dict\_write\_int()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_int) is only for serializing a single integer. width\_bytes can only be 1, 2, or 4. #### Parameters iter The dictionary iterator key The key integer Pointer to the integer value width\_bytes The width of the integer value is\_signed Whether the integer's type is signed or not #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_uint8([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const uint8\_t value) Adds a key with an unsigned, 8-bit integer value pair to the dictionary. ##### Notes There is _no_ checking for duplicate keys. There are counterpart functions for different signedness and widths, `dict_write_uint16()`, `dict_write_uint32()`, `dict_write_int8()`, `dict_write_int16()` and `dict_write_int32()`. The documentation is not repeated for brevity's sake. #### Parameters iter The dictionary iterator key The key value The unsigned, 8-bit integer value #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_uint16([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) value) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_uint32([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) value) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_int8([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const int8\_t value) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_int16([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const int16\_t value) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_int32([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const int32\_t value) [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) dict\_write\_end([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter) End a series of writing operations to a dictionary. This must be called before reading back from the dictionary. #### Parameters iter The dictionary iterator #### Returns The size in bytes of the finalized dictionary, or 0 if the parameters were invalid. [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) \* dict\_read\_begin\_from\_buffer([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const uint8\_t \*const buffer, const [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) size) Initializes the dictionary iterator with a given buffer and size, in preparation of reading key/value tuples. #### Parameters iter The dictionary iterator buffer The storage of the dictionary size The storage size of the dictionary #### Returns The first tuple in the dictionary, or NULL in case the dictionary was empty or if there was a parsing error. [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) \* dict\_read\_next([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter) Progresses the iterator to the next key/value pair. #### Parameters iter The dictionary iterator #### Returns The next tuple in the dictionary, or NULL in case the end has been reached or if there was a parsing error. [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) \* dict\_read\_first([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter) Resets the iterator back to the same state as a call to [dict\_read\_begin\_from\_buffer()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_begin_from_buffer) would do. #### Parameters iter The dictionary iterator #### Returns The first tuple in the dictionary, or NULL in case the dictionary was empty or if there was a parsing error. [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_serialize\_tuplets([DictionarySerializeCallback](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionarySerializeCallback) callback, void \* context, const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count) Utility function that takes a list of Tuplets from which a dictionary will be serialized, ready to transmit or store. ##### Note The callback will be called before the function returns, so the data that that `context` points to, can be stack allocated. #### Parameters callback The callback that will be called with the serialized data of the generated dictionary. context Pointer to any application specific data that gets passed into the callback. tuplets An array of Tuplets that need to be serialized into the dictionary. tuplets\_count The number of tuplets that follow. #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_serialize\_tuplets\_to\_buffer(const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count, uint8\_t \* buffer, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* size\_in\_out) Utility function that takes an array of Tuplets and serializes them into a dictionary with a given buffer and size. #### Parameters tuplets The array of tuplets tuplets\_count The number of tuplets in the array buffer The buffer in which to write the serialized dictionary size\_in\_out (in) The available buffer size in bytes size\_in\_out (out) The number of bytes written #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_serialize\_tuplets\_to\_buffer(const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count, uint8\_t \* buffer, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* size\_in\_out) Utility function that takes an array of Tuplets and serializes them into a dictionary with a given buffer and size. #### Parameters tuplets The array of tuplets tuplets\_count The number of tuplets in the array buffer The buffer in which to write the serialized dictionary size\_in\_out (in) The available buffer size in bytes size\_in\_out (out) The number of bytes written #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_serialize\_tuplets\_to\_buffer(const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count, uint8\_t \* buffer, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* size\_in\_out) Utility function that takes an array of Tuplets and serializes them into a dictionary with a given buffer and size. #### Parameters tuplets The array of tuplets tuplets\_count The number of tuplets in the array buffer The buffer in which to write the serialized dictionary size\_in\_out (inout) In: the available buffer size in bytes. Out: the number of bytes written. #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_serialize\_tuplets\_to\_buffer\_with\_iter([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count, uint8\_t \* buffer, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* size\_in\_out) Serializes an array of Tuplets into a dictionary with a given buffer and size. #### Parameters iter The dictionary iterator tuplets The array of tuplets tuplets\_count The number of tuplets in the array buffer The buffer in which to write the serialized dictionary size\_in\_out (in) The available buffer size in bytes size\_in\_out (out) The number of bytes written #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_serialize\_tuplets\_to\_buffer\_with\_iter([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count, uint8\_t \* buffer, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* size\_in\_out) Serializes an array of Tuplets into a dictionary with a given buffer and size. #### Parameters iter The dictionary iterator tuplets The array of tuplets tuplets\_count The number of tuplets in the array buffer The buffer in which to write the serialized dictionary size\_in\_out (in) The available buffer size in bytes size\_in\_out (out) The number of bytes written #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_serialize\_tuplets\_to\_buffer\_with\_iter([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count, uint8\_t \* buffer, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* size\_in\_out) Serializes an array of Tuplets into a dictionary with a given buffer and size. #### Parameters iter The dictionary iterator tuplets The array of tuplets tuplets\_count The number of tuplets in the array buffer The buffer in which to write the serialized dictionary size\_in\_out (inout) In: the available buffer size in bytes. Out: the number of bytes written. #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_write\_tuplet([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplet) Serializes a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) and writes the resulting [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) into a dictionary. #### Parameters iter The dictionary iterator tuplet The [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) describing the key/value pair to write #### Returns DICT\_OK, DICT\_NOT\_ENOUGH\_STORAGE or DICT\_INVALID\_ARGS [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) dict\_calc\_buffer\_size\_from\_tuplets(const [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) \*const tuplets, const uint8\_t tuplets\_count) Calculates the number of bytes that a dictionary will occupy, given one or more Tuplets that need to be stored in the dictionary. ##### Note See [dict\_calc\_buffer\_size()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_calc_buffer_size) for the formula for the calculation. #### Parameters tuplets An array of Tuplets that need to be stored in the dictionary. tuplets\_count The total number of Tuplets that follow. #### Returns The total number of bytes of storage needed. #### See Also [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) dict\_merge([DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* dest, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* dest\_max\_size\_in\_out, [DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* source, const bool update\_existing\_keys\_only, const [DictionaryKeyUpdatedCallback](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryKeyUpdatedCallback) key\_callback, void \* context) Merges entries from another "source" dictionary into a "destination" dictionary. All Tuples from the source are written into the destination dictionary, while updating the exsting Tuples with matching keys. #### Parameters dest The destination dictionary to update dest\_max\_size\_in\_out (inout) In: the maximum size of buffer backing `dest`. Out: the final size of the updated dictionary. source The source dictionary of which its Tuples will be used to update dest. update\_existing\_keys\_only Specify True if only the existing keys in `dest` should be updated. key\_callback The callback that will be called for each [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) in the merged destination dictionary. context Pointer to app specific data that will get passed in when `update_key_callback` is called. #### Returns DICT\_OK, DICT\_INVALID\_ARGS, DICT\_NOT\_ENOUGH\_STORAGE [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) \* dict\_find(const [DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) \* iter, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Tries to find a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) with specified key in a dictionary. #### Parameters iter Iterator to the dictionary to search in. key The key for which to find a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) #### Returns Pointer to a found [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple), or NULL if there was no [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) with the specified key. ## Data Structure Documentation struct DictionaryIterator An iterator can be used to iterate over the key/value tuples in an existing dictionary, using [dict\_read\_begin\_from\_buffer()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_begin_from_buffer), [dict\_read\_first()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_first) and [dict\_read\_next()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_next). An iterator can also be used to append key/value tuples to a dictionary, for example using [dict\_write\_data()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_data) or [dict\_write\_cstring()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_cstring). #### Data Fields Dictionary \* dictionary The dictionary being iterated. const void \* end Points to the first memory address after the last byte of the dictionary Points to the next [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) in the dictionary. Given the end of the Dictionary has not yet been reached: when writing, the next key/value pair will be written at the cursor. When reading, the next call to [dict\_read\_next()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_next) will return the cursor. [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) \* cursor struct Tuple Data structure for one serialized key/value tuple. ##### Note The structure is variable length! The length depends on the value data that the tuple contains. #### Data Fields [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key The key. [TupleType](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TupleType) type The type of data that the `.value` fields contains. [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) length The length of `.value` in bytes. union [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) value The value itself. The different union fields are provided for convenience, avoiding the need for manual casts. ##### Notes The array length is of incomplete length on purpose, to facilitate variable length data and because a data length of zero is valid. **Important: The integers are little endian!** struct Tuplet Non-serialized, template data structure for a key/value pair. For strings and byte arrays, it only has a pointer to the actual data. For integers, it provides storage for integers up to 32-bits wide. The [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) data structure is useful when creating dictionaries from values that are already stored in arbitrary buffers. See also [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple), with is the header of a serialized key/value pair. #### Data Fields [TupleType](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TupleType) type The type of the [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet). This determines which of the struct fields in the anonymomous union are valid. [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key The key. union [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) Anonymous union containing the reference to the [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet)'s value, being either a byte array, c-string or integer. See documentation of `.bytes`, `.cstring` and `.integer` fields. See documentation of `.bytes`, `.cstring` and `.integer` fields. ## Enum Documentation enum DictionaryResult Return values for dictionary write/conversion functions. #### Enumerators DICT\_OK The operation returned successfully. DICT\_NOT\_ENOUGH\_STORAGE There was not enough backing storage to complete the operation. DICT\_INVALID\_ARGS One or more arguments were invalid or uninitialized. DICT\_INTERNAL\_INCONSISTENCY The lengths and/or count of the dictionary its tuples are inconsistent. DICT\_MALLOC\_FAILED A requested operation required additional memory to be allocated, but the allocation failed, likely due to insufficient remaining heap memory. enum TupleType Values representing the type of data that the `value` field of a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) contains. #### Enumerators TUPLE\_BYTE\_ARRAY The value is an array of bytes. TUPLE\_CSTRING The value is a zero-terminated, UTF-8 C-string. TUPLE\_UINT The value is an unsigned integer. The tuple's `.length` field is used to determine the size of the integer (1, 2, or 4 bytes). TUPLE\_INT The value is a signed integer. The tuple's `.length` field is used to determine the size of the integer (1, 2, or 4 bytes). ## Typedef Documentation typedef struct Dictionary Dictionary typedef void(\* DictionarySerializeCallback)(const uint8\_t \*const data, const uint16\_t size, void \*context) Callback for [dict\_serialize\_tuplets()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_serialize_tuplets) utility. #### Parameters data The data of the serialized dictionary size The size of data context The context pointer as passed in to [dict\_serialize\_tuplets()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_serialize_tuplets) #### See Also [dict\_serialize\_tuplets](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_serialize_tuplets) - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) typedef void(\* DictionaryKeyUpdatedCallback)(const uint32\_t key, const Tuple \*new\_tuple, const Tuple \*old\_tuple, void \*context) Type of the callback used in [dict\_merge()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) #### Parameters key The key that is being updated. new\_tuple The new tuple. The tuple points to the actual, updated destination dictionary or NULL\_TUPLE in case there was an error (e.g. backing buffer was too small). Therefore the [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) can be used after the callback returns, until the destination dictionary storage is free'd (by the application itself). old\_tuple The values that will be replaced with `new_tuple`. The key, value and type will be equal to the previous tuple in the old destination dictionary, however the `old_tuple points to a stack-allocated copy of the old data. @param context Pointer to application specific data The storage backing `old\_tuple` can only be used during the callback and will no longer be valid after the callback returns. #### See Also [dict\_merge](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) typedef void(\* DictionaryKeyUpdatedCallback)(const uint32\_t key, const Tuple \*new\_tuple, const Tuple \*old\_tuple, void \*context) Type of the callback used in [dict\_merge()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) #### Parameters key The key that is being updated. new\_tuple The new tuple. The tuple points to the actual, updated destination dictionary or NULL\_TUPLE in case there was an error (e.g. backing buffer was too small). Therefore the [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) can be used after the callback returns, until the destination dictionary storage is free'd (by the application itself). old\_tuple The values that will be replaced with `new_tuple`. The key, value and type will be equal to the previous tuple in the old destination dictionary, however the `old_tuple points to a stack-allocated copy of the old data. @param context Pointer to application specific data The storage backing `old\_tuple` can only be used during the callback and will no longer be valid after the callback returns. #### See Also [dict\_merge](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) typedef void(\* DictionaryKeyUpdatedCallback)(const uint32\_t key, const Tuple \*new\_tuple, const Tuple \*old\_tuple, void \*context) Type of the callback used in [dict\_merge()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) #### Parameters key The key that is being updated. new\_tuple The new tuple. The tuple points to the actual, updated destination dictionary or NULL\_TUPLE in case there was an error (e.g. backing buffer was too small). Therefore the [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) can be used after the callback returns, until the destination dictionary storage is free'd (by the application itself). old\_tuple The values that will be replaced with `new_tuple`. The key, value and type will be equal to the previous tuple in the old destination dictionary, however the `old_tuple` points to a stack-allocated copy of the old data. context Pointer to application specific data The storage backing `old_tuple` can only be used during the callback and will no longer be valid after the callback returns. #### See Also [dict\_merge](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) ## Macro Definition Documentation #define TupletBytes ( \_key, \_data, \_length) Macro to create a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) with a byte array value. #### Parameters \_key The key \_data Pointer to the bytes \_length Length of the buffer #define TupletCString ( \_key, \_cstring) Macro to create a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) with a c-string value. #### Parameters \_key The key \_cstring The c-string value #define TupletInteger ( \_key, \_integer) Macro to create a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) with an integer value. #### Parameters \_key The key \_integer The integer value --- # Draw Commands Source: https://developer.repebble.com/docs/c/Graphics/Draw_Commands/ Pebble Draw Commands are a way to encode arbitrary path draw and fill calls in binary format, so that vector-like graphics can be represented on the watch. These draw commands can be loaded from resources, manipulated in place and drawn to the current graphics context. Each [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) can be an arbitrary path or a circle with optional fill or stroke. The stroke width and color of the stroke and fill are also encoded within the [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand). Paths can can be drawn open or closed. All aspects of a draw command can be modified, except for the number of points in a path (a circle only has one point, the center). Draw commands are grouped into a [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList), which can be drawn all at once. Each individual [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) can be accessed from a [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) for modification. A [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) forms the basis for [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) and [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) objects. A [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) represents a static image and can be represented by the PDC file format and can be loaded as a resource. Once you have a [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) loaded in memory you can draw it on the screen in a [LayerUpdateProc](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc) with the [gdraw\_command\_image\_draw()](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_draw). A [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) represents a single frame of an animated sequence, with multiple frames making up a single [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence), which can also be stored as a PDC and loaded as a resource. To draw a [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence), use the [gdraw\_command\_sequence\_get\_frame\_by\_elapsed()](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_get_frame_by_elapsed) to obtain the current [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) and [gdraw\_command\_frame\_draw()](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_frame_draw) to draw it. Draw commands also allow access to drawing with sub-pixel precision. The points are treated as Fixed point types in the format 13.3, so that 1/8th of a pixel precision is possible. Only the points in draw commands of the type GDrawCommandTypePrecisePath will be treated as higher precision. ## Function Documentation void gdraw\_command\_draw(GContext \* ctx, [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Draw a command. #### Parameters ctx The destination graphics context in which to draw command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) to draw [GDrawCommandType](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandType) gdraw\_command\_get\_type([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the command type. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the type #### Returns The type of the given [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) void gdraw\_command\_set\_fill\_color([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, GColor fill\_color) Set the fill color of a command. #### Parameters command ref DrawCommand for which to set the fill color fill\_color GColor to set for the fill GColor gdraw\_command\_get\_fill\_color([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the fill color of a command. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the fill color #### Returns fill color of the given [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) void gdraw\_command\_set\_stroke\_color([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, GColor stroke\_color) Set the stroke color of a command. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) for which to set the stroke color stroke\_color GColor to set for the stroke GColor gdraw\_command\_get\_stroke\_color([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the stroke color of a command. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the stroke color #### Returns The stroke color of the given [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) void gdraw\_command\_set\_stroke\_width([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, uint8\_t stroke\_width) Set the stroke width of a command. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) for which to set the stroke width stroke\_width stroke width to set for the command uint8\_t gdraw\_command\_get\_stroke\_width([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the stroke width of a command. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the stroke width #### Returns The stroke width of the given [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) gdraw\_command\_get\_num\_points([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the number of points in a command. void gdraw\_command\_set\_point([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) point\_idx, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) point) Set the value of the point in a command at the specified index. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) for which to set the value of a point point\_idx Index of the point to set the value for point new point value to set [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) gdraw\_command\_get\_point([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) point\_idx) Get the value of a point in a command from the specified index. ##### Note The index **must** be less than the number of points #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get a point point\_idx The index to get the point for #### Returns The point in the [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) specified by point\_idx - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void gdraw\_command\_set\_radius([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) radius) Set the radius of a circle command. ##### Note This only works for commands of type GDrawCommandCircle #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to set the circle radius radius The radius to set for the circle. void gdraw\_command\_set\_radius([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) radius) Set the radius of a circle command. ##### Note This only works for commands of type GDrawCommandCircle #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to set the circle radius radius The radius to set for the circle. void gdraw\_command\_set\_radius([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) radius) Set the radius of a circle command. ##### Note This only works for commands of type GDrawCommandTypeCircle #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to set the circle radius radius The radius to set for the circle. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) gdraw\_command\_get\_radius([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the radius of a circle command. ##### Note this only works for commands of typeGDrawCommandCircle. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the circle radius #### Returns The radius in pixels if command is of type GDrawCommandCircle [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) gdraw\_command\_get\_radius([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the radius of a circle command. ##### Note this only works for commands of typeGDrawCommandCircle. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the circle radius #### Returns The radius in pixels if command is of type GDrawCommandCircle [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) gdraw\_command\_get\_radius([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Get the radius of a circle command. ##### Note this only works for commands of type GDrawCommandTypeCircle. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the circle radius #### Returns The radius in pixels if command is of type GDrawCommandTypeCircle - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void gdraw\_command\_set\_path\_open([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, bool path\_open) Set the path of a stroke command to be open. ##### Note This only works for commands of type GDrawCommandPath and GDrawCommandPrecisePath #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) for which to set the path open status path\_open true if path should be hidden void gdraw\_command\_set\_path\_open([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, bool path\_open) Set the path of a stroke command to be open. ##### Note This only works for commands of type GDrawCommandPath and GDrawCommandPrecisePath #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) for which to set the path open status path\_open true if path should be hidden void gdraw\_command\_set\_path\_open([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, bool path\_open) Set the path of a stroke command to be open. ##### Note This only works for commands of type GDrawCommandTypePath and GDrawCommandTypePrecisePath #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) for which to set the path open status path\_open true if path should be hidden - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) bool gdraw\_command\_get\_path\_open([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Return whether a stroke command path is open. ##### Note This only works for commands of type GDrawCommandPath and GDrawCommandPrecisePath #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the path open status #### Returns true if the path is open bool gdraw\_command\_get\_path\_open([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Return whether a stroke command path is open. ##### Note This only works for commands of type GDrawCommandPath and GDrawCommandPrecisePath #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the path open status #### Returns true if the path is open bool gdraw\_command\_get\_path\_open([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Return whether a stroke command path is open. ##### Note This only works for commands of type GDrawCommandTypePath and GDrawCommandTypePrecisePath #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the path open status #### Returns true if the path is open void gdraw\_command\_set\_hidden([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command, bool hidden) Set a command as hidden. This command will not be drawn when [gdraw\_command\_draw](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_draw) is called with this command. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) for which to set the hidden status hidden true if command should be hidden bool gdraw\_command\_get\_hidden([GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* command) Return whether a command is hidden. #### Parameters command [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) from which to get the hidden status #### Returns true if command is hidden void gdraw\_command\_frame\_draw(GContext \* ctx, [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence, [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) \* frame, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) offset) Draw a frame. #### Parameters ctx The destination graphics context in which to draw sequence The sequence from which the frame comes from (this is required) frame Frame to draw offset Offset from draw context origin to draw the frame void gdraw\_command\_frame\_set\_duration([GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) \* frame, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) duration) Set the duration of the frame. #### Parameters frame [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) for which to set the duration duration duration of the frame in milliseconds [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) gdraw\_command\_frame\_get\_duration([GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) \* frame) Get the duration of the frame. #### Parameters frame [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) from which to get the duration #### Returns duration of the frame in milliseconds [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* gdraw\_command\_image\_create\_with\_resource([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) resource\_id) Creates a [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) from the specified resource (PDC file) #### Parameters resource\_id Resource containing data to load and create [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) from. #### Returns [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) pointer if the resource was loaded, NULL otherwise [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* gdraw\_command\_image\_clone([GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* image) Creates a [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) as a copy from a given image. #### Parameters image Image to copy. #### Returns cloned image or NULL if the operation failed void gdraw\_command\_image\_destroy([GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* image) Deletes the [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) structure and frees associated data. #### Parameters image Pointer to the image to free (delete) void gdraw\_command\_image\_draw(GContext \* ctx, [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* image, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) offset) Draw an image. #### Parameters ctx The destination graphics context in which to draw image Image to draw offset Offset from draw context origin to draw the image [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) gdraw\_command\_image\_get\_bounds\_size([GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* image) Get size of the bounding box surrounding all draw commands in the image. This bounding box can be used to set the graphics context or layer bounds when drawing the image. #### Parameters image [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) from which to get the bounding box size #### Returns bounding box size void gdraw\_command\_image\_set\_bounds\_size([GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* image, [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) size) Set size of the bounding box surrounding all draw commands in the image. This bounding box can be used to set the graphics context or layer bounds when drawing the image. #### Parameters image [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) for which to set the bounding box size size bounding box size [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) \* gdraw\_command\_image\_get\_command\_list([GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) \* image) Get the command list of the image. #### Parameters image [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) from which to get the command list #### Returns command list void gdraw\_command\_list\_iterate([GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) \* command\_list, [GDrawCommandListIteratorCb](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandListIteratorCb) handle\_command, void \* callback\_context) Iterate over all commands in a command list. #### Parameters command\_list [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) over which to iterate handle\_command iterator callback callback\_context context pointer to be passed into the iterator callback void gdraw\_command\_list\_draw(GContext \* ctx, [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) \* command\_list) Draw all commands in a command list. #### Parameters ctx The destination graphics context in which to draw command\_list list of commands to draw [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) \* gdraw\_command\_list\_get\_command([GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) \* command\_list, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) command\_idx) Get the command at the specified index. ##### Note the specified index must be less than the number of commands in the list #### Parameters command\_list [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) from which to get a command command\_idx index of the command to get #### Returns pointer to [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) at the specified index [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) gdraw\_command\_list\_get\_num\_commands([GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) \* command\_list) Get the number of commands in the list. #### Parameters command\_list [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) from which to get the number of commands #### Returns number of commands in command list [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* gdraw\_command\_sequence\_create\_with\_resource([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) resource\_id) Creates a [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from the specified resource (PDC file) #### Parameters resource\_id Resource containing data to load and create [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from. #### Returns [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) pointer if the resource was loaded, NULL otherwise [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* gdraw\_command\_sequence\_clone([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Creates a [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) as a copy from a given sequence. #### Parameters sequence Sequence to copy #### Returns cloned sequence or NULL if the operation failed - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void gdraw\_command\_sequence\_destroy([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Deletes the [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) structure and frees associated data. #### Parameters image Pointer to the sequence to destroy void gdraw\_command\_sequence\_destroy([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Deletes the [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) structure and frees associated data. #### Parameters image Pointer to the sequence to destroy void gdraw\_command\_sequence\_destroy([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Deletes the [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) structure and frees associated data. #### Parameters sequence Pointer to the sequence to destroy [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) \* gdraw\_command\_sequence\_get\_frame\_by\_elapsed([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) elapsed\_ms) Get the frame that should be shown after the specified amount of elapsed time The last frame will be returned if the elapsed time exceeds the total time. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from which to get the frame elapsed\_ms elapsed time in milliseconds #### Returns pointer to [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) that should be displayed at the elapsed time [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) \* gdraw\_command\_sequence\_get\_frame\_by\_index([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) index) Get the frame at the specified index. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from which to get the frame index Index of frame to get #### Returns pointer to [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) at the specified index [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) gdraw\_command\_sequence\_get\_bounds\_size([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Get the size of the bounding box surrounding all draw commands in the sequence. This bounding box can be used to set the graphics context or layer bounds when drawing the frames in the sequence. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from which to get the bounds #### Returns bounding box size void gdraw\_command\_sequence\_set\_bounds\_size([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence, [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) size) Set size of the bounding box surrounding all draw commands in the sequence. This bounding box can be used to set the graphics context or layer bounds when drawing the frames in the sequence. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) for which to set the bounds size bounding box size [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) gdraw\_command\_sequence\_get\_play\_count([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Get the play count of the sequence. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from which to get the play count #### Returns play count of sequence void gdraw\_command\_sequence\_set\_play\_count([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) play\_count) Set the play count of the sequence. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) for which to set the play count play\_count play count [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) gdraw\_command\_sequence\_get\_total\_duration([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Get the total duration of the sequence. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from which to get the total duration #### Returns total duration of the sequence in milliseconds [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) gdraw\_command\_sequence\_get\_num\_frames([GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) \* sequence) Get the number of frames in the sequence. #### Parameters sequence [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from which to get the number of frames #### Returns number of frames in the sequence [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) \* gdraw\_command\_frame\_get\_command\_list([GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) \* frame) Get the command list of the frame. #### Parameters frame [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) from which to get the command list #### Returns command list ## Enum Documentation enum GDrawCommandType #### Enumerators GDrawCommandTypeInvalid Invalid draw command type. GDrawCommandTypePath Arbitrary path draw command type. GDrawCommandTypeCircle Circle draw command type. GDrawCommandTypePrecisePath Arbitrary path drawn with sub-pixel precision (1/8th precision) ## Typedef Documentation typedef struct [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) GDrawCommand Draw commands are the basic building block of the draw command system, encoding the type of command to draw, the stroke width and color, fill color, and points that define the path (or center of a circle. typedef struct [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) GDrawCommandFrame Draw command frames contain a list of commands to draw for that frame and a duration, indicating the length of time for which the frame should be drawn in an animation sequence. Frames form the building blocks of a [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence), which consists of multiple frames. typedef struct [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) GDrawCommandImage Draw command images contain a list of commands that can be drawn. An image can be loaded from PDC file data. typedef struct [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) GDrawCommandList Draw command lists contain a list of commands that can be iterated over and drawn all at once. typedef bool(\* GDrawCommandListIteratorCb)(GDrawCommand \*command, uint32\_t index, void \*context) Callback for iterating over draw command list. #### Parameters command current [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) in iteration index index of the current command in the list context context pointer for the iteration operation #### Returns true if the iteration should continue after this command is processed typedef struct [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) GDrawCommandSequence Draw command sequences allow the animation of frames over time. Each sequence has a list of frames that can be accessed by the elapsed duration of the animation (not maintained internally) or by index. Sequences can be loaded from PDC file data. --- # Drawing Paths Source: https://developer.repebble.com/docs/c/Graphics/Drawing_Paths/ Functions to draw polygons into a graphics context Code example: ``` static GPath \*s\_my\_path\_ptr = NULL;static const GPathInfo BOLT\_PATH\_INFO = {.num\_points = 6,.points = (GPoint []) {{21, 0}, {14, 26}, {28, 26}, {7, 60}, {14, 34}, {0, 34}}};// .update\_proc of my\_layer:void my\_layer\_update\_proc(Layer \*my\_layer, GContext\* ctx) {// Fill the path:graphics\_context\_set\_fill\_color(ctx, GColorWhite);gpath\_draw\_filled(ctx, s\_my\_path\_ptr);// Stroke the path:graphics\_context\_set\_stroke\_color(ctx, GColorBlack);gpath\_draw\_outline(ctx, s\_my\_path\_ptr);}void setup\_my\_path(void) {s\_my\_path\_ptr = gpath\_create(&BOLT\_PATH\_INFO);// Rotate 15 degrees:gpath\_rotate\_to(s\_my\_path\_ptr, TRIG\_MAX\_ANGLE / 360 \* 15);// Translate by (5, 5):gpath\_move\_to(s\_my\_path\_ptr, GPoint(5, 5));}// For brevity, the setup of my\_layer is not written out... ``` ## Function Documentation [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) \* gpath\_create(const [GPathInfo](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPathInfo) \* init) Creates a new [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) on the heap based on a series of points described by a [GPathInfo](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPathInfo). Values after initialization: - `num_points` and `points` pointer: copied from the [GPathInfo](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPathInfo). - `rotation`: 0 - `offset`: (0, 0) #### Returns A pointer to the [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath). `NULL` if the [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) could not be created void gpath\_destroy([GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) \* gpath) Free a dynamically allocated gpath created with [gpath\_create()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_create) void gpath\_draw\_filled(GContext \* ctx, [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) \* path) Draws the fill of a path into a graphics context, using the current fill color, relative to the drawing area as set up by the layering system. #### Parameters ctx The graphics context to draw into path The path to fill #### See Also [graphics\_context\_set\_fill\_color()](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_fill_color) void gpath\_draw\_outline(GContext \* ctx, [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) \* path) Draws the outline of a path into a graphics context, using the current stroke color and width, relative to the drawing area as set up by the layering system. The first and last points in the path do have a line between them. #### Parameters ctx The graphics context to draw into path The path to draw #### See Also [graphics\_context\_set\_stroke\_color()](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_stroke_color) [gpath\_draw\_outline\_open()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline_open) void gpath\_rotate\_to([GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) \* path, int32\_t angle) Sets the absolute rotation of the path. The current rotation will be replaced by the specified angle. ##### Note Setting a rotation does not affect the points in the path directly. The rotation is applied on-the-fly during drawing, either using [gpath\_draw\_filled()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_filled) or [gpath\_draw\_outline()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline). #### Parameters path The path onto which to set the rotation angle The absolute angle of the rotation. The angle is represented in the same way that is used with [sin\_lookup()](https://developer.repebble.com/docs/c/Foundation/Math.md#sin_lookup). See [TRIG\_MAX\_ANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_ANGLE) for more information. void gpath\_move\_to([GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) \* path, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) point) Sets the absolute offset of the path. The current translation will be replaced by the specified offset. ##### Note Setting a translation does not affect the points in the path directly. The translation is applied on-the-fly during drawing, either using [gpath\_draw\_filled()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_filled) or [gpath\_draw\_outline()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline). #### Parameters path The path onto which to set the translation point The point which is used as the vector for the translation. void gpath\_draw\_outline\_open(GContext \* ctx, [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) \* path) Draws an open outline of a path into a graphics context, using the current stroke color and width, relative to the drawing area as set up by the layering system. The first and last points in the path do not have a line between them. #### Parameters ctx The graphics context to draw into path The path to draw #### See Also [graphics\_context\_set\_stroke\_color()](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_stroke_color) [gpath\_draw\_outline()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline) ## Data Structure Documentation struct GPath Data structure describing a path, plus its rotation and translation. ##### Note See the remark with [GPathInfo](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPathInfo) #### Data Fields [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_points The number of points in the `points` array. [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* points Pointer to an array of points. int32\_t rotation The rotation that will be used when drawing the path with [gpath\_draw\_filled()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_filled) or [gpath\_draw\_outline()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline) [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) offset The translation that will to be used when drawing the path with [gpath\_draw\_filled()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_filled) or [gpath\_draw\_outline()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline) struct GPathInfo Data structure describing a naked path. ##### Note Note that this data structure only refers to an array of points; the points are not stored inside this data structure itself. In most cases, one cannot use a stack-allocated array of GPoints. Instead one often needs to provide longer-lived (static or "global") storage for the points. #### Data Fields [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_points The number of points in the `points` array. [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* points Pointer to an array of points. --- # Drawing Primitives Source: https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives/ Functions to draw into a graphics context Use these drawing functions inside a Layer's `.update_proc` drawing callback. A `GContext` is passed into this callback as an argument. This `GContext` can then be used with all of the drawing functions which are documented below. See [Graphics Context](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md) for more information about the graphics context. Refer to [User Interface Layers chapter in the Pebble Developer Guides](https://developer.repebble.com/guides/user-interfaces/layers.md) (chapter "Layers") for a conceptual overview of the drawing system, Layers and relevant code examples. Other drawing functions and related documentation: - [Drawing Text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md) - [Drawing Paths](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md) - [Graphics Types](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md) ## Function Documentation void graphics\_draw\_pixel(GContext \* ctx, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) point) Draws a pixel at given point in the current stroke color. #### Parameters ctx The destination graphics context in which to draw point The point at which to draw the pixel void graphics\_draw\_line(GContext \* ctx, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) p0, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) p1) Draws line in the current stroke color, current stroke width and AA flag. #### Parameters ctx The destination graphics context in which to draw p0 The starting point of the line p1 The ending point of the line void graphics\_draw\_rect(GContext \* ctx, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect) Draws a 1-pixel wide rectangle outline in the current stroke color. #### Parameters ctx The destination graphics context in which to draw rect The rectangle for which to draw the outline void graphics\_fill\_rect(GContext \* ctx, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) corner\_radius, [GCornerMask](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerMask) corner\_mask) Fills a rectangle with the current fill color, optionally rounding all or a selection of its corners. #### Parameters ctx The destination graphics context in which to draw rect The rectangle to fill corner\_radius The rounding radius of the corners in pixels (maximum is 8 pixels) corner\_mask Bitmask of the corners that need to be rounded. #### See Also [GCornerMask](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerMask) void graphics\_draw\_circle(GContext \* ctx, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) p, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) radius) Draws the outline of a circle in the current stroke color. #### Parameters ctx The destination graphics context in which to draw p The center point of the circle radius The radius in pixels void graphics\_fill\_circle(GContext \* ctx, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) p, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) radius) Fills a circle in the current fill color. #### Parameters ctx The destination graphics context in which to draw p The center point of the circle radius The radius in pixels void graphics\_draw\_round\_rect(GContext \* ctx, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) radius) Draws the outline of a rounded rectangle in the current stroke color. #### Parameters ctx The destination graphics context in which to draw rect The rectangle defining the dimensions of the rounded rectangle to draw radius The corner radius in pixels void graphics\_draw\_bitmap\_in\_rect(GContext \* ctx, const GBitmap \* bitmap, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect) Draws a bitmap into the graphics context, inside the specified rectangle. ##### Note If the size of `rect` is smaller than the size of the bitmap, the bitmap will be clipped on right and bottom edges. If the size of `rect` is larger than the size of the bitmap, the bitmap will be tiled automatically in both horizontal and vertical directions, effectively drawing a repeating pattern. #### Parameters ctx The destination graphics context in which to draw the bitmap bitmap The bitmap to draw rect The rectangle in which to draw the bitmap #### See Also GBitmap GContext GBitmap \* graphics\_capture\_frame\_buffer(GContext \* ctx) A shortcut to capture the framebuffer in the native format of the watch. #### See Also [graphics\_capture\_frame\_buffer\_format](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_capture_frame_buffer_format) GBitmap \* graphics\_capture\_frame\_buffer\_format(GContext \* ctx, [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) format) Captures the frame buffer for direct access, using the given format. Graphics functions will not affect the frame buffer while it is captured. The frame buffer is released when `graphics_release_frame_buffer` is called. The frame buffer must be released before the end of a layer's `.update_proc` for the layer to be drawn properly. While the frame buffer is captured calling `graphics_capture_frame_buffer` will fail and return `NULL`. ##### Note When writing to the frame buffer, you should respect the visible boundaries of a window on the screen. Use layer\_get\_frame(window\_get\_root\_layer(window)).origin to obtain its position relative to the frame buffer. For example, drawing to (5, 5) in the frame buffer while the window is transitioning to the left with its origin at (-20, 0) would effectively draw that point at (25, 5) relative to the window. For this reason you should consider the window's root layer frame when calculating drawing coordinates. #### Parameters ctx The graphics context providing the frame buffer format The format in which the framebuffer should be captured. Supported formats are GBitmapFormat1Bit and GBitmapFormat8Bit. #### Returns A pointer to the frame buffer. `NULL` if failed. #### See Also GBitmap [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) [layer\_get\_frame](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_frame) [window\_get\_root\_layer](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_root_layer) bool graphics\_release\_frame\_buffer(GContext \* ctx, GBitmap \* buffer) Releases the frame buffer. Must be called before the end of a layer's `.update_proc` for the layer to be drawn properly. If `buffer` does not point to the address previously returned by `graphics_capture_frame_buffer` the frame buffer will not be released. #### Parameters ctx The graphics context providing the frame buffer buffer The pointer to frame buffer #### Returns True if the frame buffer was released successfully bool graphics\_frame\_buffer\_is\_captured(GContext \* ctx) Whether or not the frame buffer has been captured by `graphics_capture_frame_buffer`. Graphics functions will not affect the frame buffer until it has been released by `graphics_release_frame_buffer`. #### Parameters ctx The graphics context providing the frame buffer #### Returns True if the frame buffer has been captured void graphics\_draw\_rotated\_bitmap(GContext \* ctx, GBitmap \* src, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) src\_ic, int rotation, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) dest\_ic) Draws a rotated bitmap with a memory-sensitive 2x anti-aliasing technique (using ray-finding instead of super-sampling), which is thresholded into a b/w bitmap for 1-bit and color blended for 8-bit. ##### Note This API has performance limitations that can degrade user experience. Use sparingly. #### Parameters ctx The destination graphics context in which to draw src The source bitmap to draw src\_ic Instance center (single point unaffected by rotation) relative to source bitmap rotation Angle of rotation. Rotation is an integer between 0 (no rotation) and TRIG\_MAX\_ANGLE (360 degree rotation). Use [DEG\_TO\_TRIGANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE) to easily convert degrees to the appropriate value. dest\_ic Where to draw the instance center of the rotated bitmap in the context. void graphics\_draw\_arc(GContext \* ctx, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, [GOvalScaleMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleMode) scale\_mode, int32\_t angle\_start, int32\_t angle\_end) Draws a line arc clockwise between `angle_start` and `angle_end`, where 0° is the top of the circle. If the difference between `angle_start` and `angle_end` is greater than 360°, a full circle will be drawn. #### Parameters ctx The destination graphics context in which to draw using the current stroke color and antialiasing setting. rect The reference rectangle to derive the center point and radius (see scale\_mode). scale\_mode Determines how rect will be used to derive the center point and radius. angle\_start Radial starting angle. Use [DEG\_TO\_TRIGANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE) to easily convert degrees to the appropriate value. angle\_end Radial finishing angle. If smaller than `angle_start`, nothing will be drawn. void graphics\_fill\_radial(GContext \* ctx, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, [GOvalScaleMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleMode) scale\_mode, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) inset\_thickness, int32\_t angle\_start, int32\_t angle\_end) Fills a circle clockwise between `angle_start` and `angle_end`, where 0° is the top of the circle. If the difference between `angle_start` and `angle_end` is greater than 360°, a full circle will be drawn and filled. If `angle_start` is greater than `angle_end` nothing will be drawn. ##### Note A simple example is drawing a 'Pacman' shape, with a starting angle of -225°, and ending angle of 45°. By setting `inset_thickness` to a non-zero value (such as 30) this example will produce the letter C. #### Parameters ctx The destination graphics context in which to draw using the current fill color and antialiasing setting. rect The reference rectangle to derive the center point and radius (see scale). scale\_mode Determines how rect will be used to derive the center point and radius. inset\_thickness Describes how thick in pixels the radial will be drawn towards its center measured from the outside. angle\_start Radial starting angle. Use [DEG\_TO\_TRIGANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE) to easily convert degrees to the appropriate value. angle\_end Radial finishing angle. If smaller than `angle_start`, nothing will be drawn. [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) gpoint\_from\_polar([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, [GOvalScaleMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleMode) scale\_mode, int32\_t angle) Calculates a [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) located at the angle provided on the perimeter of a circle defined by the provided [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect). #### Parameters rect The reference rectangle to derive the center point and radius (see scale\_mode). scale\_mode Determines how rect will be used to derive the center point and radius. angle The angle at which the point on the circle's perimeter should be calculated. Use [DEG\_TO\_TRIGANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE) to easily convert degrees to the appropriate value. #### Returns The point on the circle's perimeter. [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) grect\_centered\_from\_polar([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, [GOvalScaleMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleMode) scale\_mode, int32\_t angle, [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) size) Calculates a rectangle centered on the perimeter of a circle at a given angle. Use this to construct rectangles that follow the perimeter of a circle as an input for graphics\_fill\_radial\_internal or graphics\_draw\_arc\_internal, e.g. to draw circles every 30 degrees on a watchface. #### Parameters rect The reference rectangle to derive the circle's center point and radius (see scale\_mode). scale\_mode Determines how rect will be used to derive the circle's center point and radius. angle The angle at which the point on the circle's perimeter should be calculated. Use [DEG\_TO\_TRIGANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE) to easily convert degrees to the appropriate value. size Width and height of the desired rectangle. #### Returns The rectangle centered on the circle's perimeter. ## Enum Documentation enum GCornerMask Bit mask values to specify the corners of a rectangle. The values can be combines using binary OR (`|`), For example: the mask to indicate top left and bottom right corners can: be created as follows: `(GCornerTopLeft | GCornerBottomRight)` #### Enumerators GCornerNone No corners. GCornerTopLeft Top-Left corner. GCornerTopRight Top-Right corner. GCornerBottomLeft Bottom-Left corner. GCornerBottomRight Bottom-Right corner. GCornersAll All corners. GCornersTop Top corners. GCornersBottom Bottom corners. GCornersLeft Left corners. GCornersRight Right corners. enum GOvalScaleMode Values to specify how a given rectangle should be used to derive an oval shape. graphics\_fill\_radial\_internal graphics\_draw\_arc\_internal gpoint\_from\_polar\_internal grect\_centered\_from\_polar #### Enumerators GOvalScaleModeFitCircle Places a circle at the center of the rectangle, with a diameter that matches the rectangle's shortest side. GOvalScaleModeFillCircle Places a circle at the center of the rectangle, with a diameter that matches the rectangle's longest side. The circle may overflow the bounds of the rectangle. --- # Drawing Text Source: https://developer.repebble.com/docs/c/Graphics/Drawing_Text/ Functions to draw text into a graphics context See [Graphics Context](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md) for more information about the graphics context. Other drawing functions and related documentation: - [Drawing Primitives](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md) - [Drawing Paths](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md) - [Graphics Types](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md) ## Function Documentation GTextAttributes \* graphics\_text\_attributes\_create(void) Creates an instance of GTextAttributes for advanced control when rendering text. #### Returns New instance of GTextAttributes #### See Also [graphics\_draw\_text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_draw_text) void graphics\_text\_attributes\_destroy(GTextAttributes \* text\_attributes) Destroys a previously created instance of GTextAttributes. void graphics\_text\_attributes\_restore\_default\_text\_flow(GTextAttributes \* text\_attributes) Restores text flow to the rectangular default. #### Parameters text\_attributes The attributes for which to disable text flow #### See Also [graphics\_text\_attributes\_enable\_screen\_text\_flow](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_enable_screen_text_flow) [text\_layer\_restore\_default\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_restore_default_text_flow_and_paging) void graphics\_text\_attributes\_enable\_screen\_text\_flow(GTextAttributes \* text\_attributes, uint8\_t inset) Enables text flow that follows the boundaries of the screen. #### Parameters text\_attributes The attributes for which text flow should be enabled inset Additional amount of pixels to inset to the inside of the screen for text flow calculation. Can be zero. #### See Also [graphics\_text\_attributes\_restore\_default\_text\_flow](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_restore_default_text_flow) [text\_layer\_enable\_screen\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_enable_screen_text_flow_and_paging) void graphics\_text\_attributes\_restore\_default\_paging(GTextAttributes \* text\_attributes) Restores paging and locked content origin to the defaults. #### Parameters text\_attributes The attributes for which to restore paging and locked content origin #### See Also [graphics\_text\_attributes\_enable\_paging](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_enable_paging) [text\_layer\_restore\_default\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_restore_default_text_flow_and_paging) void graphics\_text\_attributes\_enable\_paging(GTextAttributes \* text\_attributes, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) content\_origin\_on\_screen, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) paging\_on\_screen) Enables paging and locks the text flow calculation to a fixed point on the screen. #### Parameters text\_attributes Attributes for which to enable paging and locked content origin content\_origin\_on\_screen Absolute coordinate on the screen where the text content starts before an animation or scrolling takes place. Usually the frame's origin of a layer in screen coordinates. paging\_on\_screen Rectangle in absolute coordinates on the screen that describes where text content pages. Usually the container's absolute frame in screen coordinates. #### See Also [graphics\_text\_attributes\_restore\_default\_paging](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_restore_default_paging) [graphics\_text\_attributes\_enable\_screen\_text\_flow](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_enable_screen_text_flow) [text\_layer\_enable\_screen\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_enable_screen_text_flow_and_paging) [layer\_convert\_point\_to\_screen](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_convert_point_to_screen) void graphics\_draw\_text(GContext \* ctx, const char \* text, [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) const font, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) box, const [GTextOverflowMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowMode) overflow\_mode, const [GTextAlignment](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignment) alignment, GTextAttributes \* text\_attributes) Draw text into the current graphics context, using the context's current text color. The text will be drawn inside a box with the specified dimensions and configuration, with clipping occuring automatically. #### Parameters ctx The destination graphics context in which to draw text The zero terminated UTF-8 string to draw font The font in which the text should be set box The bounding box in which to draw the text. The first line of text will be drawn against the top of the box. overflow\_mode The overflow behavior, in case the text is larger than what fits inside the box. alignment The horizontal alignment of the text text\_attributes Optional text attributes to describe the characteristics of the text [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) graphics\_text\_layout\_get\_content\_size(const char \* text, [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) const font, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) box, const [GTextOverflowMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowMode) overflow\_mode, const [GTextAlignment](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignment) alignment) Obtain the maximum size that a text with given font, overflow mode and alignment occupies within a given rectangular constraint. #### Parameters text The zero terminated UTF-8 string for which to calculate the size font The font in which the text should be set while calculating the size box The bounding box in which the text should be constrained overflow\_mode The overflow behavior, in case the text is larger than what fits inside the box. alignment The horizontal alignment of the text #### Returns The maximum size occupied by the text #### See Also app\_graphics\_text\_layout\_get\_content\_size\_with\_attributes [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) graphics\_text\_layout\_get\_content\_size\_with\_attributes(const char \* text, [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) const font, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) box, const [GTextOverflowMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowMode) overflow\_mode, const [GTextAlignment](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignment) alignment, GTextAttributes \* text\_attributes) Obtain the maximum size that a text with given font, overflow mode and alignment occupies within a given rectangular constraint. #### Parameters text The zero terminated UTF-8 string for which to calculate the size font The font in which the text should be set while calculating the size box The bounding box in which the text should be constrained overflow\_mode The overflow behavior, in case the text is larger than what fits inside the box. alignment The horizontal alignment of the text text\_attributes Optional text attributes to describe the characteristics of the text #### Returns The maximum size occupied by the text #### See Also app\_graphics\_text\_layout\_get\_content\_size ## Enum Documentation enum GTextOverflowMode Text overflow mode controls the way text overflows when the string that is drawn does not fit inside the area constraint. graphics\_draw\_text text\_layer\_set\_overflow\_mode #### Enumerators GTextOverflowModeWordWrap On overflow, wrap words to a new line below the current one. Once vertical space is consumed, the last line may be clipped. GTextOverflowModeTrailingEllipsis On overflow, wrap words to a new line below the current one. Once vertical space is consumed, truncate as needed to fit a trailing ellipsis (...). Clipping may occur if the vertical space cannot accomodate the first line of text. GTextOverflowModeFill Acts like GTextOverflowModeTrailingEllipsis, plus trims leading and trailing newlines, while treating all other newlines as spaces. enum GTextAlignment Text aligment controls the way the text is aligned inside the box the text is drawn into. graphics\_draw\_text text\_layer\_set\_text\_alignment #### Enumerators GTextAlignmentLeft Aligns the text to the left of the drawing box. GTextAlignmentCenter Aligns the text centered inside the drawing box. GTextAlignmentRight Aligns the text to the right of the drawing box. ## Typedef Documentation typedef struct GTextAttributes GTextAttributes --- # Event Service Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/ APIs to handle event services. Pebble OS provides you with a set of APIs for handling event services, like accelerometer taps, app focus for interactivity, battery states, Bluetooth connections, and tick timers to call when a time component has changed. You use event services to be notified, for example, when something happens on Pebble, like losing the connection to the phone or when a notification covers the screen and requires your Pebble game to pause. ## Modules #### [AccelerometerService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md)   #### [AlarmService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md)   #### [AppFocusService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md)   #### [BacklightService](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md)   #### [BatteryStateService](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md)   #### [CompassService](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md)   #### [ConnectionService](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md) Determine what the Pebble watch is connected to #### [HealthService](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md)   #### [TickTimerService](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md) Handling time components #### [TouchService](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md)   --- # Exit Reason Source: https://developer.repebble.com/docs/c/Foundation/Exit_Reason/ API for the application to notify the system of the reason it will exit. If the application has not specified an exit reason before it exits, then the exit reason will default to APP\_EXIT\_NOT\_SPECIFIED. Only an application can set its exit reason. The system will not modify it. ## Function Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void exit\_reason\_set([AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) exit\_reason) Set the app exit reason to a new reason. #### Parameters reason The new app exit reason void exit\_reason\_set([AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) exit\_reason) Set the app exit reason to a new reason. #### Parameters reason The new app exit reason void exit\_reason\_set([AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) exit\_reason) Set the app exit reason to a new reason. #### Parameters exit\_reason The new app exit reason ## Enum Documentation enum AppExitReason [AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) is used to notify the system of the reason of an application exiting, which may affect the part of the system UI that is presented after the application terminates. #### Enumerators APP\_EXIT\_NOT\_SPECIFIED Exit reason not specified. APP\_EXIT\_ACTION\_PERFORMED\_SUCCESSFULLY Application performed an action when it exited. NUM\_EXIT\_REASONS Number of [AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) options. --- # File Formats Source: https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/ ## Modules #### [PBI File Format](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PBI_File_Format.md)   #### [PNG8 File Format](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PNG8_File_Format.md)   --- # Fonts Source: https://developer.repebble.com/docs/c/Graphics/Fonts/ Custom and system fonts. Pebble OS provides you with a wide range of system fonts that you can use when you need to display and render text or numbers in your Pebble watchface or watchapp. If you want to use a system font, you call [fonts\_get\_system\_font()](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_get_system_font) and simply pass it the name of the system font you want. To use a custom font, call [fonts\_load\_custom\_font()](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_load_custom_font). The sample code feature\_custom\_font shows how you can do this programmatically, using a font resource to convert a TrueType font into a rasterized version of that font at a specified font size. For example: ``` GFont custom\_font = fonts\_load\_custom\_font(resource\_get\_handle(RESOURCE\_ID\_FONT\_OSP\_DIN\_44)); ``` Raster Gothic Condensed is the font used throughout the Pebble system, largely because it is optimized for monochromatic displays. Pebble selected this font because it allows a relatively large number of characters to be displayed on a single line, also because the font has an excellent readability vs. size ratio. Refer to the chapter [App Resources in the Pebble Developer Guides](https://developer.repebble.com/guides/pebble-apps/resources/) which explains how to work with font resources and embed a font into your app. ## Function Documentation [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) fonts\_get\_system\_font(const char \* font\_key) Loads a system font corresponding to the specified font key. ##### Note This may load a font from the flash peripheral into RAM. #### Parameters font\_key The string key of the font to load. See [System Fonts](https://developer.repebble.com/guides/app-resources/system-fonts.md) guide for a list of system fonts. #### Returns An opaque pointer to the loaded font, or, a pointer to the default (fallback) font if the specified font cannot be loaded. [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) fonts\_load\_custom\_font([ResHandle](https://developer.repebble.com/docs/c/Foundation/Resources.md#ResHandle) handle) Loads a custom font. ##### Note this may load a font from the flash peripheral into RAM. #### Parameters handle The resource handle of the font to load. See resource\_ids.auto.h for a list of resource IDs, and use `resource_get_handle()` to obtain the resource handle. #### Returns An opaque pointer to the loaded font, or a pointer to the default (fallback) font if the specified font cannot be loaded. #### See Also Read the [App Resources](https://developer.repebble.com/guides/pebble-apps/resources/) guide on how to embed a font into your app. void fonts\_unload\_custom\_font([GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) font) Unloads the specified custom font and frees the memory that is occupied by it. ##### Note When an application exits, the system automatically unloads all fonts that have been loaded. #### Parameters font The font to unload. ## Typedef Documentation typedef struct FontInfo FontInfo typedef FontInfo \* GFont Pointer to opaque font data structure. #### See Also [fonts\_load\_custom\_font()](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_load_custom_font) [text\_layer\_set\_font()](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_font) [graphics\_draw\_text()](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_draw_text) --- # Format Source: https://developer.repebble.com/docs/c/Standard_C/Format/ Standard formatting. If you're looking for input/output functions, check out the [Logging](https://developer.repebble.com/docs/c/Foundation/Logging.md) API. ## Function Documentation int snprintf(char \* str, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n, const \* fmt, ...) Format a string into a buffer. The Pebble-supported format specifiers are displayed below. | Specifier | Output | Example | | --- | --- | --- | | d or i | Signed decimal integer | 294 | | u | Unsigned decimal integer | 7235 | | o | Unsigned octal | 610 | | x | Unsigned hexadecimal integer | 8b2 | | X | Unsigned hexadecimal integer (uppercase) | 8B2 | | c | Character | h | | s | Null-terminated string of characters | pebble | | p | Pointer address | 0xb8000000 | | % | A % followed by another % character will write a single % to the stream. | % | Length specifiers can also be combined with the format specifiers above: | | u o x X | c | s | p | n | | --- | --- | --- | --- | --- | --- | | (none) | unsigned int | int | char\* | void\* | int\* | | h | unsigned short int | | | | short int\* | | l | unsigned long int | wint\_t | wchar\_t\* | | long int\* | #### Parameters str The string buffer to write the formatted string into n The maximum size of the buffer fmt The C formatting string #### Returns The number of bytes written --- # Foundation Source: https://developer.repebble.com/docs/c/Foundation/ The core of the Pebble SDK. The Pebble SDK consists of different frameworks that are organized by functionality. Each framework includes an API and provides access to the software libraries supported natively by Pebble OS. You use these interfaces (APIs) in the C programming language to write software for the Pebble platform. Frameworks are grouped hierarchically into Foundation, Graphics, Standard C and User Interface. The Foundation framework is the core of the Pebble OS. ## Modules #### [Alloy](https://developer.repebble.com/docs/c/Foundation/Alloy.md)   #### [App](https://developer.repebble.com/docs/c/Foundation/App.md) App entry point and event loop. #### [App Communication](https://developer.repebble.com/docs/c/Foundation/App_Communication.md) API for interacting with the Pebble communication subsystem. #### [App Glance](https://developer.repebble.com/docs/c/Foundation/App_Glance.md) API for the application to modify its "glance" i.e. app menu subtitle. #### [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md)   #### [AppSync](https://developer.repebble.com/docs/c/Foundation/AppSync.md) UI synchronization layer for AppMessage #### [AppWorker](https://developer.repebble.com/docs/c/Foundation/AppWorker.md) Runs in the background, and can communicate with the foreground app. #### [DataLogging](https://developer.repebble.com/docs/c/Foundation/DataLogging.md) Enables logging data asynchronously to a mobile app #### [DataStructures](https://developer.repebble.com/docs/c/Foundation/DataStructures/)   #### [Dictation](https://developer.repebble.com/docs/c/Foundation/Dictation.md)   #### [Dictionary](https://developer.repebble.com/docs/c/Foundation/Dictionary.md) Data serialization utilities #### [Event Service](https://developer.repebble.com/docs/c/Foundation/Event_Service.md) APIs to handle event services. #### [Exit Reason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md) API for the application to notify the system of the reason it will exit. #### [Internationalization](https://developer.repebble.com/docs/c/Foundation/Internationalization.md) Internationalization & Localization APIs #### [Launch Reason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md) API for checking what caused the application to launch. #### [Logging](https://developer.repebble.com/docs/c/Foundation/Logging.md) Functions related to logging from apps. #### [Math](https://developer.repebble.com/docs/c/Foundation/Math.md) Math routines. #### [Memory Management](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md) Utility functions for managing an application's memory. #### [Platform](https://developer.repebble.com/docs/c/Foundation/Platform.md)   #### [Resources](https://developer.repebble.com/docs/c/Foundation/Resources.md) Managing application resources #### [Storage](https://developer.repebble.com/docs/c/Foundation/Storage.md) A mechanism to store persistent application data and state #### [Timer](https://developer.repebble.com/docs/c/Foundation/Timer.md) Can be used to execute some code at some point in the future. #### [Wakeup](https://developer.repebble.com/docs/c/Foundation/Wakeup.md) Allows applications to schedule to be launched even if they are not running. #### [Wall Time](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md) Functions, data structures and other things related to wall clock time. #### [WatchInfo](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md) Provides information about the watch itself. --- # Gesture Recognizers Source: https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers/ The APIs on this page will only work with SDK 4.9+. Low-level touch-gesture recognizers for building fully custom touch interactions on touch-capable hardware. A recognizer watches the raw touch stream for one gesture — a tap, a pan (single-axis drag) or a swipe — and calls a [RecognizerEventCb](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEventCb) as the gesture progresses. Create one with a per-gesture constructor ([tap\_recognizer\_create](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_create), [pan\_recognizer\_create](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_create), [swipe\_recognizer\_create](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_create)), attach it to a [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md), and read the current gesture data from the event callback. Recognizers are the building block for a custom scroll or drag. For an ordinary scrollable list you do not need recognizers at all: on touch hardware [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) already scrolls by touch. Reach for recognizers to build an interaction the built-in list does not give you — a custom menu, a drag, or acting on a raw tap or swipe. Note that a bare [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md) does not scroll by touch on its own; you drive it with a pan recognizer, as below. ### Usage example: scrolling a custom menu A [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md) holds your own content but does not scroll by touch by itself. Attach a vertical pan recognizer to the window and feed its delta into [scroll\_layer\_set\_content\_offset()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_offset) to scroll the content by finger: ``` static ScrollLayer \*s\_scroll;static int16\_t s\_base;// content offset committed on Completestatic void pan\_handler(const Recognizer \*recognizer, RecognizerEvent event) {switch (event) {case RecognizerEvent\_Updated: {// delta\_since\_start is (0, 0) at Start, so the content does not jump.GPoint d = pan\_recognizer\_get\_delta\_since\_start(recognizer);scroll\_layer\_set\_content\_offset(s\_scroll, GPoint(0, s\_base + d.y), false);break;}case RecognizerEvent\_Completed:s\_base = scroll\_layer\_get\_content\_offset(s\_scroll).y;// commitbreak;case RecognizerEvent\_Cancelled:scroll\_layer\_set\_content\_offset(s\_scroll, GPoint(0, s\_base), true);// roll backbreak;default:break;}}static void window\_load(Window \*window) {Layer \*root = window\_get\_root\_layer(window);s\_scroll = scroll\_layer\_create(layer\_get\_bounds(root));scroll\_layer\_set\_content\_size(s\_scroll, GSize(layer\_get\_bounds(root).size.w,total\_content\_height));// Add your custom row layers as children of s\_scroll here.layer\_add\_child(root, scroll\_layer\_get\_layer(s\_scroll));// The window owns the recognizer and destroys it when the window unloads.Recognizer \*pan = pan\_recognizer\_create(pan\_handler, NULL, PanAxis\_Vertical);window\_attach\_recognizer(window, pan);} ``` While the global system recognizer set is live, attaching alone is not enough: a window opts out of it with [window\_set\_touch\_bridge\_disabled()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_touch_bridge_disabled) (added by the touch-bridge change) so its own recognizers receive the touch stream. ## Function Documentation Recognizer \* tap\_recognizer\_create([RecognizerEventCb](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEventCb) event\_cb, void \* user\_data) Create a tap recognizer. The default recognizer recognizes a single tap from a single finger. #### Parameters event\_cb event callback user\_data user data associated with recognizer #### Returns recognizer reference Recognizer \* pan\_recognizer\_create([RecognizerEventCb](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEventCb) event\_cb, void \* user\_data, [PanAxis](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis) axis) Create a pan recognizer locked to a single axis. The recognizer stays in the Possible state until the finger moves unambiguously along _axis_ and crosses the start threshold, at which point it Starts. It Fails if the finger instead moves unambiguously along the foreign axis. #### Parameters event\_cb event callback user\_data user data associated with recognizer axis axis to which the pan is locked #### Returns recognizer reference Recognizer \* swipe\_recognizer\_create([RecognizerEventCb](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEventCb) event\_cb, void \* user\_data, uint8\_t direction\_mask) Create a swipe recognizer that accepts the directions set in _direction\_mask_. The recognizer stays Possible while tracking the path and Completes on liftoff if the path is a fast, straight, long-enough flick whose direction is in the mask; otherwise it Fails. #### Parameters event\_cb event callback user\_data user data associated with recognizer direction\_mask bitwise-OR of the [SwipeDirection](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection) values to accept #### Returns recognizer reference [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) tap\_recognizer\_get\_tap\_point(const Recognizer \* recognizer) Get the coordinate of the recognized tap. The coordinate is taken from the last position update, not the liftoff. Valid once the recognizer has completed. #### Parameters recognizer recognizer from which to get the tap coordinate #### Returns tap coordinate [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) pan\_recognizer\_get\_total\_delta(const Recognizer \* recognizer) Get the total movement of the pan since the touchdown point. Component-wise. #### Parameters recognizer recognizer from which to get the delta #### Returns total delta from touchdown [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) pan\_recognizer\_get\_delta\_since\_start(const Recognizer \* recognizer) Get the movement of the pan since it Started (i.e. since the start threshold was crossed). This is exactly (0, 0) at the instant the recognizer transitions to Started, so live scroll that consumes this value does not jump at gesture start. Component-wise. #### Parameters recognizer recognizer from which to get the delta #### Returns delta since the pan started [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) pan\_recognizer\_get\_delta\_since\_prev(const Recognizer \* recognizer) Get the movement of the pan since the previous position update. Component-wise. #### Parameters recognizer recognizer from which to get the delta #### Returns delta since the previous event [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) pan\_recognizer\_get\_velocity(const Recognizer \* recognizer) Get the current velocity of the pan, in pixels per second, component-wise. Computed over the most-recent events within a short time window (see PAN\_VELOCITY\_WINDOW\_MS). Zero when the elapsed time across the sampled events is zero. #### Parameters recognizer recognizer from which to get the velocity #### Returns velocity in px/s [SwipeDirection](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection) swipe\_recognizer\_get\_direction(const Recognizer \* recognizer) Get the recognized swipe direction. Valid once the recognizer has Completed; otherwise SwipeDirection\_None. #### Parameters recognizer recognizer from which to get the direction #### Returns recognized swipe direction [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) swipe\_recognizer\_get\_velocity(const Recognizer \* recognizer) Get the velocity of the swipe, in pixels per second, component-wise. Computed over the most-recent events within a short time window. Zero when the elapsed time is zero. #### Parameters recognizer recognizer from which to get the velocity #### Returns velocity in px/s void recognizer\_destroy(Recognizer \* recognizer) Destroy an un-owned recognizer. If a recognizer is not owned, this will destroy the recognizer freeing it's data and calling the destructor (see recognizer\_set\_on\_destroy), if set. If it is owned, this will do nothing. #### Parameters recognizer [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) to destroy void recognizer\_set\_simultaneous\_with(Recognizer \* recognizer, [RecognizerSimultaneousWithCb](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerSimultaneousWithCb) simultaneous\_with\_cb) Specify a callback to determine whether a recognizer should be evaluated simultaneously with another recognizer. #### Parameters recognizer recognizer to modify simultaneous\_with\_cb callback that determines whether this recognizer will be evaluated simultaneous with another recognizer void recognizer\_set\_fail\_after(Recognizer \* recognizer, Recognizer \* fail\_after) Tell a recognizer to only evaluate after the another recognizer fails. #### Parameters recognizer recognizer to modify fail\_after recognizer after which the modified gesture will be evaluated ## Enum Documentation enum RecognizerEvent #### Enumerators RecognizerEvent\_StartedRecognizerEvent\_UpdatedRecognizerEvent\_CompletedRecognizerEvent\_Cancelled enum PanAxis Axis to which a pan recognizer is locked. A pan is only recognized when the finger moves unambiguously along this axis. #### Enumerators PanAxis\_HorizontalPanAxis\_Vertical enum SwipeDirection Swipe direction, also used as a bitmask when configuring which directions a swipe recognizer accepts. Screen coordinates grow downward, so a positive y delta is a downward swipe. #### Enumerators SwipeDirection\_NoneSwipeDirection\_UpSwipeDirection\_DownSwipeDirection\_LeftSwipeDirection\_Right ## Typedef Documentation typedef struct Recognizer Recognizer typedef void(\* RecognizerEventCb)(const Recognizer \*recognizer, RecognizerEvent event\_type) User event callback. When a recognizer changes state to any state other than the failed state the user callback of this type will be called. #### Parameters recognizer recognizer affected by the event event\_type event that occurred typedef bool(\* RecognizerSimultaneousWithCb)(const Recognizer \*recognizer, const Recognizer \*simultaneous\_with) This function is called to determine whether _recognizer_ should be evaluated simultaneously with _simultaneous\_with_. #### Parameters recognizer recognizer to be tested simultaneous\_with recognizer with which _recognizer_ would be evaluated simultaneously should this test pass #### Returns true if recognizers should be evaluated simultaneously --- # Graphics Source: https://developer.repebble.com/docs/c/Graphics/ Low-level drawing routines. The Graphics framework defines a set of APIs that can be used to draw paths, primitives and text, as well as handle the loading of fonts and the setting of graphics contexts for compositing, fill color and text color. ## Modules #### [Draw Commands](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md) Pebble Draw Commands are a way to encode arbitrary path draw and fill calls in binary format, so that vector-like graphics can be represented on the watch. #### [Drawing Paths](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md) Functions to draw polygons into a graphics context #### [Drawing Primitives](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md) Functions to draw into a graphics context #### [Drawing Text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md) Functions to draw text into a graphics context #### [Fonts](https://developer.repebble.com/docs/c/Graphics/Fonts.md) Custom and system fonts. #### [Graphics Context](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md) The "canvas" into which an application draws #### [Graphics Types](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md) Basic graphics types (point, rect, size, color, bitmaps, etc.) and utility functions. --- # Graphics Context Source: https://developer.repebble.com/docs/c/Graphics/Graphics_Context/ The "canvas" into which an application draws The Pebble OS graphics engine, inspired by several notable graphics systems, including Apple’s Quartz 2D and its predecessor QuickDraw, provides your app with a canvas into which to draw, namely, the graphics context. A graphics context is the target into which graphics functions can paint, using Pebble drawing routines (see [Drawing Primitives](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md), [Drawing Paths](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md) and [Drawing Text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md)). A graphics context holds a reference to the bitmap into which to paint. It also holds the current drawing state, like the current fill color, stroke color, clipping box, drawing box, compositing mode, and so on. The GContext struct is the type representing the graphics context. For drawing in your Pebble watchface or watchapp, you won't need to create a GContext yourself. In most cases, it is provided by Pebble OS as an argument passed into a render callback (the .update\_proc of a Layer). Your app can’t call drawing functions at any given point in time: Pebble OS will request your app to render. Typically, your app will be calling out to graphics functions in the .update\_proc callback of a Layer. ## Function Documentation void graphics\_context\_set\_stroke\_color(GContext \* ctx, GColor color) Sets the current stroke color of the graphics context. #### Parameters ctx The graphics context onto which to set the stroke color color The new stroke color void graphics\_context\_set\_fill\_color(GContext \* ctx, GColor color) Sets the current fill color of the graphics context. #### Parameters ctx The graphics context onto which to set the fill color color The new fill color void graphics\_context\_set\_text\_color(GContext \* ctx, GColor color) Sets the current text color of the graphics context. #### Parameters ctx The graphics context onto which to set the text color color The new text color - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void graphics\_context\_set\_compositing\_mode(GContext \* ctx, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the current bitmap compositing mode of the graphics context. ##### Note At the moment, this only affects the bitmaps drawing operations - [graphics\_draw\_bitmap\_in\_rect()](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_bitmap_in_rect), [graphics\_draw\_rotated\_bitmap](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_rotated_bitmap), and anything that uses those APIs -, but it currently does not affect the filling or stroking operations. #### Parameters ctx The graphics context onto which to set the compositing mode mode The new compositing mode #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) [bitmap\_layer\_set\_compositing\_mode()](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_compositing_mode) void graphics\_context\_set\_compositing\_mode(GContext \* ctx, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the current bitmap compositing mode of the graphics context. ##### Note At the moment, this only affects the bitmaps drawing operations - [graphics\_draw\_bitmap\_in\_rect()](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_bitmap_in_rect), [graphics\_draw\_rotated\_bitmap](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_rotated_bitmap), and anything that uses those APIs -, but it currently does not affect the filling or stroking operations. #### Parameters ctx The graphics context onto which to set the compositing mode mode The new compositing mode #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) [bitmap\_layer\_set\_compositing\_mode()](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_compositing_mode) void graphics\_context\_set\_compositing\_mode(GContext \* ctx, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the current bitmap compositing mode of the graphics context. The default mode is GCompOpAssign i.e. bitmap transparency disabled. ##### Note At the moment, this only affects the bitmaps drawing operations - [graphics\_draw\_bitmap\_in\_rect()](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_bitmap_in_rect), [graphics\_draw\_rotated\_bitmap](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_rotated_bitmap), and anything that uses those APIs -, but it currently does not affect the filling or stroking operations. #### Parameters ctx The graphics context onto which to set the compositing mode mode The new compositing mode #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) [bitmap\_layer\_set\_compositing\_mode()](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_compositing_mode) void graphics\_context\_set\_antialiased(GContext \* ctx, bool enable) Sets whether antialiasing is applied to stroke drawing. ##### Note Default value is true. #### Parameters ctx The graphics context onto which to set the antialiasing enable True = antialiasing enabled, False = antialiasing disabled void graphics\_context\_set\_stroke\_width(GContext \* ctx, uint8\_t stroke\_width) Sets the width of the stroke for drawing routines. ##### Note If stroke width of zero is passed, it will be ignored and will not change the value stored in GContext. Currently, only odd stroke\_width values are supported. If an even value is passed in, the value will be stored as is, but the drawing routines will round down to the previous integral value when drawing. Default value is 1. #### Parameters ctx The graphics context onto which to set the stroke width stroke\_width Width in pixels of the stroke. --- # Graphics Types Source: https://developer.repebble.com/docs/c/Graphics/Graphics_Types/ Basic graphics types (point, rect, size, color, bitmaps, etc.) and utility functions. ## Modules #### [Color Definitions](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md) A list of all of the named colors available with links to the color map on the Pebble Developer website. ## Function Documentation bool gcolor\_equal(GColor8 x, GColor8 y) True if both colors are identical or both are invisible (i.e. both have alpha values of .a=0). GColor8 gcolor\_legible\_over(GColor8 background\_color) This method assists in improving the legibility of text on various background colors. It takes the background color for the region in question and computes a color for maximum legibility. #### Parameters background\_color Background color for the region in question #### Returns A legible color for the given background color bool gpoint\_equal(const [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \*const point\_a, const [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \*const point\_b) Tests whether 2 points are equal. #### Parameters point\_a Pointer to the first point point\_b Pointer to the second point #### Returns `true` if both points are equal, `false` if not. bool gsize\_equal(const [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) \* size\_a, const [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) \* size\_b) Tests whether 2 sizes are equal. #### Parameters size\_a Pointer to the first size size\_b Pointer to the second size #### Returns `true` if both sizes are equal, `false` if not. bool grect\_equal(const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_a, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_b) Tests whether 2 rectangles are equal. #### Parameters rect\_a Pointer to the first rectangle rect\_b Pointer to the second rectangle #### Returns `true` if both rectangles are equal, `false` if not. bool grect\_is\_empty(const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect) Tests whether the size of the rectangle is (0, 0). ##### Note If the width and/or height of a rectangle is negative, this function will return `true`! #### Parameters rect Pointer to the rectangle #### Returns `true` if the rectangle its size is (0, 0), or `false` if not. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void grect\_standardize([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect) Converts a rectangle's values so that the components of its size (width and/or height) are both positive. In the width and/or height are negative, the origin will offset, so that the final rectangle overlaps with the original. For example, a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) with size (-10, -5) and origin (20, 20), will be standardized to size (10, 5) and origin (10, 15). #### Parameters rect (in) The rectangle to convert. rect (out) The standardized rectangle. void grect\_standardize([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect) Converts a rectangle's values so that the components of its size (width and/or height) are both positive. In the width and/or height are negative, the origin will offset, so that the final rectangle overlaps with the original. For example, a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) with size (-10, -5) and origin (20, 20), will be standardized to size (10, 5) and origin (10, 15). #### Parameters rect (in) The rectangle to convert. rect (out) The standardized rectangle. void grect\_standardize([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect) Converts a rectangle's values so that the components of its size (width and/or height) are both positive. In the width and/or height are negative, the origin will offset, so that the final rectangle overlaps with the original. For example, a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) with size (-10, -5) and origin (20, 20), will be standardized to size (10, 5) and origin (10, 15). #### Parameters rect (inout) The rectangle to convert, set to the standardized rectangle. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void grect\_clip([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_to\_clip, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_clipper) Trim one rectangle using the edges of a second rectangle. #### Parameters rect\_to\_clip (in) The rectangle that needs to be clipped (in place). rect\_to\_clip (out) The clipped rectangle. rect\_clipper The rectangle of which the edges will serve as "scissors" in order to trim `rect_to_clip`. void grect\_clip([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_to\_clip, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_clipper) Trim one rectangle using the edges of a second rectangle. #### Parameters rect\_to\_clip (in) The rectangle that needs to be clipped (in place). rect\_to\_clip (out) The clipped rectangle. rect\_clipper The rectangle of which the edges will serve as "scissors" in order to trim `rect_to_clip`. void grect\_clip([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_to\_clip, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \*const rect\_clipper) Trim one rectangle using the edges of a second rectangle. #### Parameters rect\_to\_clip (inout) The rectangle that needs to be clipped (in place). rect\_clipper The rectangle of which the edges will serve as "scissors" in order to trim `rect_to_clip`. bool grect\_contains\_point(const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect, const [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* point) Tests whether a rectangle contains a point. #### Parameters rect The rectangle point The point #### Returns `true` if the rectangle contains the point, or `false` if it does not. [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) grect\_center\_point(const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect) Convenience function to compute the center-point of a given rectangle. This is equal to `(rect->x + rect->width / 2, rect->y + rect->height / 2)`. #### Parameters rect The rectangle for which to calculate the center point. #### Returns The point at the center of `rect` [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) grect\_crop([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, const int32\_t crop\_size\_px) Reduce the width and height of a rectangle by insetting each of the edges with a fixed inset. The returned rectangle will be centered relative to the input rectangle. ##### Note The function will trip an assertion if the crop yields a rectangle with negative width or height. #### Parameters rect The rectangle that will be inset crop\_size\_px The inset by which each of the rectangle will be inset. A positive inset value results in a smaller rectangle, while negative inset value results in a larger rectangle. #### Returns The cropped rectangle. [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) gbitmap\_get\_bytes\_per\_row(const GBitmap \* bitmap) Get the number of bytes per row in the bitmap data for the given GBitmap. On rectangular displays, this can be used as a safe way of iterating over the rows in the bitmap, since bytes per row should be set according to format. On circular displays with pixel format of GBitmapFormat8BitCircular this will return 0, and should not be used for iteration over frame buffer pixels. Instead, use [GBitmapDataRowInfo](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapDataRowInfo), which provides safe minimum and maximum x values for a given row's y value. #### Parameters bitmap A pointer to the GBitmap to get the bytes per row #### Returns The number of bytes per row of the GBitmap #### See Also [gbitmap\_get\_data](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data) [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) gbitmap\_get\_format(const GBitmap \* bitmap) Get the [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) for the GBitmap. #### Parameters bitmap A pointer to the GBitmap to get the format #### Returns The format of the given GBitmap. uint8\_t \* gbitmap\_get\_data(const GBitmap \* bitmap) Get a pointer to the raw image data section of the given GBitmap as specified by the format of the bitmap. #### Parameters bitmap A pointer to the GBitmap to get the data #### Returns pointer to the raw image data for the GBitmap #### See Also [gbitmap\_get\_bytes\_per\_row](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_bytes_per_row) GBitmap void gbitmap\_set\_data(GBitmap \* bitmap, uint8\_t \* data, [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) format, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) row\_size\_bytes, bool free\_on\_destroy) Set the bitmap data for the given GBitmap. #### Parameters bitmap A pointer to the GBitmap to set data to data A pointer to the bitmap data format the format of the bitmap data. If this is a palettized format, make sure that there is an accompanying call to [gbitmap\_set\_palette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_palette). row\_size\_bytes How many bytes a single row takes. For example, bitmap data of format GBitmapFormat1Bit must have a row size as a multiple of 4 bytes. free\_on\_destroy Set whether the data should be freed when the GBitmap is destroyed. #### See Also [gbitmap\_destroy](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy) [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) gbitmap\_get\_bounds(const GBitmap \* bitmap) Gets the bounds of the content for the GBitmap. This is set when loading the image or if changed by [gbitmap\_set\_bounds](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_bounds). #### Parameters bitmap A pointer to the GBitmap to get the bounding box from. #### Returns The bounding box for the GBitmap. #### See Also [gbitmap\_set\_bounds](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_bounds) void gbitmap\_set\_bounds(GBitmap \* bitmap, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) bounds) Set the bounds of the given GBitmap. #### Parameters bitmap A pointer to the GBitmap to set the bounding box. bounds The bounding box to set. #### See Also [gbitmap\_get\_bounds](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_bounds) GColor \* gbitmap\_get\_palette(const GBitmap \* bitmap) Get the palette for the given GBitmap. #### Parameters bitmap A pointer to the GBitmap to get the palette from. #### Returns Pointer to a GColor array containing the palette colors. #### See Also [gbitmap\_set\_palette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_palette) void gbitmap\_set\_palette(GBitmap \* bitmap, GColor \* palette, bool free\_on\_destroy) Set the palette for the given GBitmap. #### Parameters bitmap A pointer to the GBitmap to set the palette to palette The palette to be used. Make sure that the palette is large enough for the bitmap's format. free\_on\_destroy Set whether the palette data should be freed when the GBitmap is destroyed or when another palette is set. #### See Also [gbitmap\_get\_format](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_format) [gbitmap\_destroy](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy) [gbitmap\_set\_palette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_palette) GBitmap \* gbitmap\_create\_with\_resource([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) resource\_id) Creates a new GBitmap on the heap using a Pebble image file stored as a resource. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). #### Parameters resource\_id The ID of the bitmap resource to load #### Returns A pointer to the GBitmap. `NULL` if the GBitmap could not be created GBitmap \* gbitmap\_create\_with\_data(const uint8\_t \* data) Creates a new GBitmap on the heap initialized with the provided Pebble image data. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy) but the image data will not be freed automatically. The developer is responsible for keeping the image data in memory as long as the bitmap is used and releasing it after the bitmap is destroyed. ##### Note One way to generate Pebble image data is to use bitmapgen.py in the Pebble SDK to generate a .pbi file. #### Parameters data The Pebble image data. Must not be NULL. The function assumes the data to be correct; there are no sanity checks performed on the data. The data will not be copied and the pointer must remain valid for the lifetime of this GBitmap. #### Returns A pointer to the GBitmap. `NULL` if the GBitmap could not be created GBitmap \* gbitmap\_create\_as\_sub\_bitmap(const GBitmap \* base\_bitmap, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) sub\_rect) Create a new GBitmap on the heap as a sub-bitmap of a 'base' GBitmap, using a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) to indicate what portion of the base to use. The sub-bitmap will just reference the image data and palette of the base bitmap. No deep-copying occurs as a result of calling this function, thus the caller is responsible for making sure the base bitmap and palette will remain available when using the sub-bitmap. Note that you should not destroy the parent bitmap until the sub\_bitmap has been destroyed. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). #### Parameters base\_bitmap (in) The bitmap that the sub-bitmap of which the image data will be used by the sub-bitmap sub\_rect The rectangle within the image data of the base bitmap. The bounds of the base bitmap will be used to clip `sub_rect`. #### Returns A pointer to the GBitmap. `NULL` if the GBitmap could not be created GBitmap \* gbitmap\_create\_from\_png\_data(const uint8\_t \* png\_data, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) png\_data\_size) Create a GBitmap based on raw PNG data. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). The developer is responsible for freeing png\_data following this call. ##### Note PNG decoding currently supports 1,2,4 and 8 bit palettized and grayscale images. #### Parameters png\_data PNG image data. png\_data\_size PNG image size in bytes. #### Returns A pointer to the GBitmap. `NULL` if the GBitmap could not be created GBitmap \* gbitmap\_create\_blank([GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) size, [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) format) Creates a new blank GBitmap on the heap initialized to zeroes. In the case that the format indicates a palettized bitmap, a palette of appropriate size will also be allocated on the heap. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). #### Parameters size The Pebble image dimensions as a [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize). format The [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) the created image should be in. #### Returns A pointer to the GBitmap. `NULL` if the GBitmap could not be created GBitmap \* gbitmap\_create\_blank\_with\_palette([GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) size, [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) format, GColor \* palette, bool free\_on\_destroy) Creates a new blank GBitmap on the heap, initialized to zeroes, and assigns it the given palette. No deep-copying of the palette occurs, so the caller is responsible for making sure the palette remains available when using the resulting bitmap. Management of that memory can be handed off to the system with the free\_on\_destroy argument. #### Parameters size The Pebble image dimensions as a [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize). format the [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) the created image and palette should be in. palette a pointer to a palette that is to be used for this GBitmap. The palette should be large enough to hold enough colors for the specified format. For example, GBitmapFormat2BitPalette should have 4 colors, since 2^2 = 4. free\_on\_destroy Set whether the palette data should be freed along with the bitmap data when the GBitmap is destroyed. #### Returns A Pointer to the GBitmap. `NULL` if the GBitmap could not be created. GBitmap \* gbitmap\_create\_palettized\_from\_1bit(const GBitmap \* src\_bitmap) Given a 1-bit GBitmap, create a new bitmap of format GBitmapFormat1BitPalette. The new data buffer is allocated on the heap, and a 2-color palette is allocated as well. ##### Note The new bitmap does not depend on any data from src\_bitmap, so src\_bitmap can be freed without worry. #### Parameters src\_bitmap A GBitmap of format GBitmapFormat1Bit which is to be copied into a newly created GBitmap of format GBitmapFormat1BitPalettized. #### Returns The newly created 1-bit palettized GBitmap, or NULL if there is not sufficient space. void gbitmap\_destroy(GBitmap \* bitmap) Destroy a GBitmap. This must be called for every bitmap that's been created with gbitmap\_create\_\*. This function will also free the memory of the bitmap data (bitmap-\>addr) if the bitmap was created with [gbitmap\_create\_blank()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_blank) or [gbitmap\_create\_with\_resource()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_with_resource). If the GBitmap was created with [gbitmap\_create\_with\_data()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_with_data), you must release the memory after calling [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). GBitmapSequence \* gbitmap\_sequence\_create\_with\_resource([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) resource\_id) Creates a GBitmapSequence from the specified resource (APNG/PNG files) #### Parameters resource\_id Resource to load and create GBitmapSequence from. #### Returns GBitmapSequence pointer if the resource was loaded, NULL otherwise bool gbitmap\_sequence\_update\_bitmap\_next\_frame(GBitmapSequence \* bitmap\_sequence, GBitmap \* bitmap, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* delay\_ms) Updates the contents of the bitmap sequence to the next frame and optionally returns the delay in milliseconds until the next frame. ##### Note GBitmap must be large enough to accommodate the bitmap\_sequence image [gbitmap\_sequence\_get\_bitmap\_size](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_get_bitmap_size) #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence bitmap Pointer to the initialized GBitmap in which to render the bitmap sequence delay\_ms (out) If not NULL, returns the delay in milliseconds until the next frame. #### Returns True if frame was rendered. False if all frames (and loops) have been rendered for the sequence. Will also return false if frame could not be rendered (includes out of memory errors). bool gbitmap\_sequence\_update\_bitmap\_by\_elapsed(GBitmapSequence \* bitmap\_sequence, GBitmap \* bitmap, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) elapsed\_ms) Updates the contents of the bitmap sequence to the frame at elapsed in the sequence. For looping animations this accounts for the loop, for example an animation of 1 second that is configured to loop 2 times updated to 1500 ms elapsed time will display the sequence frame at 500 ms. Elapsed time is the time from the start of the animation, and will be ignored if it is for a time earlier than the last rendered frame. ##### Notes GBitmap must be large enough to accommodate the bitmap\_sequence image [gbitmap\_sequence\_get\_bitmap\_size](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_get_bitmap_size) This function is disabled for play\_count 0 #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence bitmap Pointer to the initialized GBitmap in which to render the bitmap sequence elapsed\_ms Elapsed time in milliseconds in the sequence relative to start #### Returns True if a frame was rendered. False if all frames (and loops) have already been rendered for the sequence. Will also return false if frame could not be rendered (includes out of memory errors). void gbitmap\_sequence\_destroy(GBitmapSequence \* bitmap\_sequence) Deletes the GBitmapSequence structure and frees any allocated memory/decoder\_data. #### Parameters bitmap\_sequence Pointer to the bitmap sequence to free (delete) bool gbitmap\_sequence\_restart(GBitmapSequence \* bitmap\_sequence) Restarts the GBitmapSequence to the first frame [gbitmap\_sequence\_update\_bitmap\_next\_frame](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_update_bitmap_next_frame). #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence #### Returns True if sequence was restarted, false otherwise int32\_t gbitmap\_sequence\_get\_current\_frame\_idx(GBitmapSequence \* bitmap\_sequence) This function gets the current frame number for the bitmap sequence. #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence #### Returns index of current frame in the current loop of the bitmap sequence [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) gbitmap\_sequence\_get\_total\_num\_frames(GBitmapSequence \* bitmap\_sequence) This function sets the total number of frames for the bitmap sequence. #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence #### Returns number of frames contained in a single loop of the bitmap sequence [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) gbitmap\_sequence\_get\_play\_count(GBitmapSequence \* bitmap\_sequence) This function gets the play count (number of times to repeat) the bitmap sequence. ##### Note This value is initialized by the bitmap sequence data, and is modified by [gbitmap\_sequence\_set\_play\_count](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_set_play_count) #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence #### Returns Play count of bitmap sequence, PLAY\_COUNT\_INFINITE for infinite looping void gbitmap\_sequence\_set\_play\_count(GBitmapSequence \* bitmap\_sequence, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) play\_count) This function sets the play count (number of times to repeat) the bitmap sequence. #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence play\_count Number of times to repeat the bitmap sequence with 0 disabling update\_by\_elapsed and update\_next\_frame, and PLAY\_COUNT\_INFINITE for infinite looping of the animation [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) gbitmap\_sequence\_get\_bitmap\_size(GBitmapSequence \* bitmap\_sequence) This function gets the minimum required size (dimensions) necessary to render the bitmap sequence to a GBitmap using the /ref gbitmap\_sequence\_update\_bitmap\_next\_frame. #### Parameters bitmap\_sequence Pointer to loaded bitmap sequence #### Returns Dimensions required to render the bitmap sequence to a GBitmap [GBitmapDataRowInfo](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapDataRowInfo) gbitmap\_get\_data\_row\_info(const GBitmap \* bitmap, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) y) Provides information about a pixel data row. ##### Note This function does not respect the bitmap's bounds but purely operates on the pixel data. This function works with every bitmap format including GBitmapFormat1Bit. The result of the function for invalid rows is undefined. #### Parameters bitmap A pointer to the GBitmap to get row info y Absolute row number in the pixel data, independent from the bitmap's bounds #### Returns Description of the row #### See Also [gbitmap\_get\_data](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data) - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void grect\_align([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* inside\_rect, const [GAlign](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlign) alignment, const bool clip) Aligns one rectangle within another rectangle, using an alignment parameter. The relative coordinate systems of both rectangles are assumed to be the same. When clip is true, `rect` is also clipped by the constraint. #### Parameters rect (in) The rectangle to align (in place) rect (out) The aligned and optionally clipped rectangle inside\_rect The rectangle in which to align `rect` alignment Determines the alignment of `rect` within `inside_rect` by specifying what edges of should overlap. clip Determines whether `rect` should be trimmed using the edges of `inside_rect` in case `rect` extends outside of the area that `inside_rect` covers after the alignment. void grect\_align([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* inside\_rect, const [GAlign](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlign) alignment, const bool clip) Aligns one rectangle within another rectangle, using an alignment parameter. The relative coordinate systems of both rectangles are assumed to be the same. When clip is true, `rect` is also clipped by the constraint. #### Parameters rect (in) The rectangle to align (in place) rect (out) The aligned and optionally clipped rectangle inside\_rect The rectangle in which to align `rect` alignment Determines the alignment of `rect` within `inside_rect` by specifying what edges of should overlap. clip Determines whether `rect` should be trimmed using the edges of `inside_rect` in case `rect` extends outside of the area that `inside_rect` covers after the alignment. void grect\_align([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* rect, const [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* inside\_rect, const [GAlign](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlign) alignment, const bool clip) Aligns one rectangle within another rectangle, using an alignment parameter. The relative coordinate systems of both rectangles are assumed to be the same. When clip is true, `rect` is also clipped by the constraint. #### Parameters rect (inout) The rectangle to align (in place), set to the aligned and optionally clipped rectangle inside\_rect The rectangle in which to align `rect` alignment Determines the alignment of `rect` within `inside_rect` by specifying what edges of should overlap. clip Determines whether `rect` should be trimmed using the edges of `inside_rect` in case `rect` extends outside of the area that `inside_rect` covers after the alignment. [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) grect\_inset([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect, [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) insets) Returns a rectangle that is shrinked or expanded by the given edge insets. ##### Notes The rectangle is standardized and then the inset parameters are applied. If the resulting rectangle would have a negative height or width, a GRectZero is returned. Use this function in together with the [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro ``` GRect r\_inset\_all\_sides = grect\_inset(r, GEdgeInsets(10));GRect r\_inset\_vertical\_horizontal = grect\_inset(r, GEdgeInsets(10, 20));GRect r\_expand\_top\_right\_shrink\_bottom\_left = grect\_inset(r, GEdgeInsets(-10, -10, 10, 10)); ``` #### Parameters rect The rectangle that will be inset insets The insets that will be applied #### Returns The resulting rectangle ## Data Structure Documentation struct GBitmapDataRowInfo Description of a single data row in the pixel data of a bitmap. ##### Note This data type describes the actual pixel data of a bitmap and does not respect the bitmap's bounds. #### Data Fields uint8\_t \* data Address of the byte at column 0 of a given data row in a bitmap. Use this to calculate the memory address of a pixel. For GBitmapFormat8BitCircular or GBitmapFormat8Bit this would be: `uint8_t *pixel_addr = row_info.addr + x`. Note that this byte can be outside of the valid range for this row. For example: The first valid pixel (`min_x=76`) of a row might start at 76 bytes after the given data pointer (assuming that 1 pixel is represented as 1 byte as in GBitmapFormat8BitCircular or GBitmapFormat8Bit). int16\_t min\_x The absolute column of a first valid pixel for a given data row. int16\_t max\_x The absolute column of the last valid pixel for a given data row. For optimization reasons the result can be anywhere between grect\_get\_max\_x(bitmap\_bounds) - 1 and the physical data boundary. #### See Also [gbitmap\_get\_data\_row\_info](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info) [gbitmap\_get\_data](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data) [gbitmap\_get\_bounds](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_bounds) struct GEdgeInsets Represents insets for four sides. Negative values mean a side extends. #### Data Fields int16\_t top The inset at the top of an object. int16\_t right The inset at the right of an object. int16\_t bottom The inset at the bottom of an object. int16\_t left The inset at the left of an object. #### See Also [grect\_inset](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_inset) struct GPoint Represents a point in a 2-dimensional coordinate system. ##### Note Conventionally, the origin of Pebble's 2D coordinate system is in the upper, lefthand corner its x-axis extends to the right and its y-axis extends to the bottom of the screen. #### Data Fields int16\_t x The x-coordinate. int16\_t y The y-coordinate. struct GRect Represents a rectangle and defining it using the origin of the upper-lefthand corner and its size. #### Data Fields [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) origin The coordinate of the upper-lefthand corner point of the rectangle. [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) size The size of the rectangle. struct GSize Represents a 2-dimensional size. #### Data Fields int16\_t w The width. int16\_t h The height. ## Union Documentation struct GColor8 #### Data Fields uint8\_t argbstruct GColor8 ## Enum Documentation enum GBitmapFormat The format of a GBitmap can either be 1-bit or 8-bit. #### Enumerators GBitmapFormat1BitGBitmapFormat8BitGBitmapFormat1BitPaletteGBitmapFormat2BitPaletteGBitmapFormat4BitPaletteGBitmapFormat8BitCircular enum GAlign Values to specify how two things should be aligned relative to each other. ![](https://developer.repebble.com/assets/images/docs/c/aplite/galign.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/galign.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/galign.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/galign.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/galign.png) bitmap\_layer\_set\_alignment() #### Enumerators GAlignCenter Align by centering. GAlignTopLeft Align by making the top edges overlap and left edges overlap. GAlignTopRight Align by making the top edges overlap and left edges overlap. GAlignTop Align by making the top edges overlap and centered horizontally. GAlignLeft Align by making the left edges overlap and centered vertically. GAlignBottom Align by making the bottom edges overlap and centered horizontally. GAlignRight Align by making the right edges overlap and centered vertically. GAlignBottomRight Align by making the bottom edges overlap and right edges overlap. GAlignBottomLeft Align by making the bottom edges overlap and left edges overlap. enum GCompOp Values to specify how the source image should be composited onto the destination image. ![](https://developer.repebble.com/assets/images/docs/c/aplite/compops.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/compops.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/compops.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/compops.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/compops.png) Contrived example of how the different compositing modes affect drawing. Often, the "destination image" is the render buffer and thus contains the image of what has been drawn before or "underneath". For color displays, only two compositing modes are supported, GCompOpAssign and GCompOpSet. The behavior of other compositing modes are undefined and may change in the future. Transparency can be achieved using GCompOpSet and requires pixel values with alpha value .a \< 3. bitmap\_layer\_set\_compositing\_mode() graphics\_context\_set\_compositing\_mode() graphics\_draw\_bitmap\_in\_rect() graphics\_draw\_rotated\_bitmap() #### Enumerators GCompOpAssign Assign the pixel values of the source image to the destination pixels, effectively replacing the previous values for those pixels. For color displays, when drawing a palettized or 8-bit GBitmap image, the opacity value is ignored. GCompOpAssignInverted ##### Note For bitmaps with a format different from GBitmapFormat1Bit, this mode is not supported and the resulting behavior is undefined. Assign the **inverted** pixel values of the source image to the destination pixels, effectively replacing the previous values for those pixels. GCompOpOr ##### Note For bitmaps with a format different from GBitmapFormat1Bit, this mode is not supported and the resulting behavior is undefined. Use the boolean operator `OR` to composite the source and destination pixels. The visual result of this compositing mode is the source's white pixels are painted onto the destination and the source's black pixels are treated as clear. GCompOpAnd ##### Note For bitmaps with a format different from GBitmapFormat1Bit, this mode is not supported and the resulting behavior is undefined. Use the boolean operator `AND` to composite the source and destination pixels. The visual result of this compositing mode is the source's black pixels are painted onto the destination and the source's white pixels are treated as clear. GCompOpClear ##### Note For bitmaps with a format different from GBitmapFormat1Bit, this mode is not supported and the resulting behavior is undefined. Clears the bits in the destination image, using the source image as mask. The visual result of this compositing mode is that for the parts where the source image is white, the destination image will be painted black. Other parts will be left untouched. GCompOpSet ##### Note For bitmaps of the format GBitmapFormat1Bit, the visual result of this compositing mode is that for the parts where the source image is black, the destination image will be painted white. Other parts will be left untouched. Sets the bits in the destination image, using the source image as mask. This mode is required to apply any transparency of your bitmap. ## Typedef Documentation typedef GColor8 GColor typedef struct GBitmap GBitmap typedef struct GBitmapSequence GBitmapSequence typedef struct GContext GContext ## Macro Definition Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define GColorFromRGBA ( red, green, blue, alpha) Convert RGBA to GColor. #### Parameters red Red value from 0 - 255 green Green value from 0 - 255 blue Blue value from 0 - 255 alpha Alpha value from 0 - 255 #### Returns GColor created from the RGB values #define GColorFromRGBA ( red, green, blue, alpha) Convert RGBA to GColor. #### Parameters red Red value from 0 - 255 green Green value from 0 - 255 blue Blue value from 0 - 255 alpha Alpha value from 0 - 255 #### Returns GColor created from the RGB values #define GColorFromRGBA ( red, green, blue, alpha) Convert RGBA to GColor. #### Parameters red Red value from 0 - 255 green Green value from 0 - 255 blue Blue value from 0 - 255 alpha Alpha value from 0 - 255 #### Returns GColor created from the RGBA values #define GColorFromRGB ( red, green, blue) Convert RGB to GColor. #### Parameters red Red value from 0 - 255 green Green value from 0 - 255 blue Blue value from 0 - 255 #### Returns GColor created from the RGB values #define GColorFromHEX ( v) Convert hex integer to GColor. #### Parameters v Integer hex value (e.g. 0x64ff46) #### Returns GColor created from the hex value - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define COLOR\_FALLBACK ( color, bw) Convenience macro allowing use of a fallback color for black and white platforms. On color platforms, the first expression will be chosen, the second otherwise. #define COLOR\_FALLBACK ( color, bw) Convenience macro allowing use of a fallback color for black and white platforms. On color platforms, the first expression will be chosen, the second otherwise. #define COLOR\_FALLBACK ( color, bw) Convenience macro allowing use of a fallback color for black and white platforms. On color platforms, the first expression will be chosen, the second otherwise. #define PBL\_IF\_RECT\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On platforms with rectangular screen, the first expression will be chosen, the second otherwise. #define PBL\_IF\_ROUND\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On platforms with round screen, the first expression will be chosen, the second otherwise. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define PBL\_IF\_BW\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On black& white platforms, the first expression will be chosen, the second otherwise. #define PBL\_IF\_BW\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On black& white platforms, the first expression will be chosen, the second otherwise. #define PBL\_IF\_BW\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On black& white platforms, the first expression will be chosen, the second otherwise. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define PBL\_IF\_COLOR\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On color platforms, the first expression will be chosen, the second otherwise. #define PBL\_IF\_COLOR\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On color platforms, the first expression will be chosen, the second otherwise. #define PBL\_IF\_COLOR\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expression depending on the screen of the platform. On color platforms, the first expression will be chosen, the second otherwise. #define GPoint ( x, y) Convenience macro to make a [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint). #define GPointZero [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint)(0, 0) Convenience macro to make a [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) at (0, 0). #define GSize ( w, h) Convenience macro to make a [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize). #define GSizeZero [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize)(0, 0) Convenience macro to make a [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) of (0, 0). #define GRect ( x, y, w, h) Convenience macro to make a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect). #define GRectZero [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect)(0, 0, 0, 0) Convenience macro to make a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) of ((0, 0), (0, 0)). #define PLAY\_COUNT\_INFINITE UINT32\_MAX Repeat Sequence or animation indefinitely. #define PLAY\_DURATION\_INFINITE UINT32\_MAX Duration of Sequence or animation is infinite. #define GEdgeInsets4 ( t, r, b, l) helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro #define GEdgeInsets3 ( t, rl, b) helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro #define GEdgeInsets2 ( tb, rl) helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro #define GEdgeInsets1 ( trbl) helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro #define GEdgeInsetsN ( \_1, \_2, \_3, \_4, NAME, ...) helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define GEdgeInsets ( ...) Convenience macro to make a [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) This macro follows the CSS shorthand notation where you can call it with. - just one value [GEdgeInsets(v1)](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) to configure all edges with v1 ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v1, .bottom = v1, .left = v1} - two values v1, v2 to configure a vertical and horizontal inset as ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v1, .left = v2} - three values v1, v2, v3 to configure it with ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v3, .left = v2} - four values v1, v2, v3, v4 to configure it with ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v3, .left = v4} #### See Also grect\_insets #define GEdgeInsets ( ...) Convenience macro to make a [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) This macro follows the CSS shorthand notation where you can call it with. - just one value [GEdgeInsets(v1)](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) to configure all edges with v1 ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v1, .bottom = v1, .left = v1} - two values v1, v2 to configure a vertical and horizontal inset as ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v1, .left = v2} - three values v1, v2, v3 to configure it with ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v3, .left = v2} - four values v1, v2, v3, v4 to configure it with ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v3, .left = v4} #### See Also grect\_insets #define GEdgeInsets ( ...) Convenience macro to make a [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) This macro follows the CSS shorthand notation where you can call it with. - just one value [GEdgeInsets(v1)](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) to configure all edges with v1 ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v1, .bottom = v1, .left = v1} - two values v1, v2 to configure a vertical and horizontal inset as ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v1, .left = v2} - three values v1, v2, v3 to configure it with ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v3, .left = v2} - four values v1, v2, v3, v4 to configure it with ([GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets)){.top = v1, .right = v2, .bottom = v3, .left = v4} #### See Also [grect\_inset](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_inset) --- # HealthService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService/ Get access to health information like step count, sleep totals, etc. The HealthService provides your app access to the step count and sleep activity of the user. ## Function Documentation [HealthValue](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthValue) health\_service\_sum([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end) Return the sum of a [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric)'s values over a time range. The `time_start` and `time_end` parameters define the range of time you want the sum for. ##### Note The value returned will be based on daily totals, weighted for the length of the specified time range. This may change in the future. #### Parameters metric The metric to query for data. time\_start UTC time of the earliest data item to incorporate into the sum. time\_end UTC time of the most recent data item to incorporate into the sum. #### Returns The sum of that metric over the given time range, if available. [HealthValue](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthValue) health\_service\_sum\_today([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric) Convenience wrapper for [health\_service\_sum()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum) that returns the sum for today. #### Parameters metric The metric to query. #### Returns The sum of that metric's data for today, if available. [HealthValue](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthValue) health\_service\_peek\_current\_value([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric) Convenience function for peeking at the current value of a metric. This is useful for metrics like HealthMetricHeartRateBPM that represent instantaneous values. It is NOT applicable for metrics like HealthMetricStepCount that must be accumulated over time (it will return 0 if passed that type of metric). This call is equivalent to calling `health_service_aggregate_averaged(metric, time(NULL), time(NULL), HealthAggregationAvg, HealthServiceTimeScopeOnce)` #### Parameters metric The metric to query. #### Returns The current value of that metric, if available. [HealthValue](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthValue) health\_service\_sum\_averaged([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Return the value of a metric's sum over a given time range between `time_start` and `time_end`. Using this call you can specify the time range that you are interested in getting the average for, as well as a `scope` specifier on how to compute an average of the sum. For example, if you want to get the average number of steps taken from 12 AM (midnight) to 9 AM across all days you would specify: ``` time\_t time\_start = time\_start\_of\_today();time\_t time\_end = time\_start + (9 \* SECONDS\_PER\_HOUR);HealthValue value = health\_service\_sum\_averaged(HealthMetricStepCount, time\_start,time\_end, HealthServiceTimeScopeDaily); ``` If you want the average number of steps taken on a weekday (Monday to Friday) and today is a Monday (in the local timezone) you would specify: ``` time\_start = time\_start\_of\_today();time\_end = time\_start + SECONDS\_PER\_DAY;HealthValue value = health\_service\_sum\_averaged(HealthMetricStepCount, time\_start,time\_end, HealthServiceTimeScopeDailyWeekdayOrWeekend); ``` Note that this call is the same as calling `health_service_aggregate_averaged(metric, time_start, time_end, HealthAggregationSum, scope)` #### Parameters metric Which [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) to query. time\_start UTC time of the start of the query interval. time\_end UTC time of the end of the query interval. scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. #### Returns The average of the sum of the given metric over the given time range, if available. [HealthValue](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthValue) health\_service\_aggregate\_averaged([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthAggregation](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation) aggregation, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Return the value of an aggregated metric over a given time range. This call is more flexible than health\_service\_sum\_averaged because it lets you specify which aggregation function to perform. The aggregation function `aggregation` is applied to the metric `metric` over the given time range `time_start` to `time_end` first, and then an average is computed based on the passed in `scope`. For example, if you want to get the average number of steps taken from 12 AM (midnight) to 9 AM across all days you would specify: ``` time\_t time\_start = time\_start\_of\_today();time\_t time\_end = time\_start + (9 \* SECONDS\_PER\_HOUR);HealthValue value = health\_service\_aggregate\_averaged(HealthMetricStepCount, time\_start,time\_end, HealthAggregationSum, HealthServiceTimeScopeDaily); ``` If you want to compute the average heart rate on Mondays and today is a Monday, you would specify: ``` time\_t time\_start = time\_start\_of\_today(),time\_t time\_end = time\_start + SECONDS\_PER\_DAY,HealthValue value = health\_service\_aggregate\_averaged(HealthMetricHeartRateBPM, time\_start,time\_end, HealthAggregationAvg, HealthServiceTimeScopeWeekly); ``` To get the average of the minimum heart rate seen on Mondays for example, you would instead pass in `HealthAggregationMin` Certain [HealthAggregation](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation) operations are only applicable to certain types of metrics. See the notes above on [HealthAggregation](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation) for details. Use [health\_service\_metric\_aggregate\_averaged\_accessible](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_aggregate_averaged_accessible) to check for applicability at run time. #### Parameters metric Which [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) to query. time\_start UTC time of the start of the query interval. time\_end UTC time of the end of the query interval. aggregation the aggregation function to perform on the metric. This operation is performed across the passed in time range `time_start` to `time_end`. scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. Use `HealthServiceTimeScopeOnce` to not compute an average. #### Returns The average of the aggregation performed on the given metric over the given time range, if available. [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) health\_service\_peek\_current\_activities(void) Return a [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) containing a set of bits, one set for each activity that is currently active. #### Returns A bitmask with zero or more [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) bits set as appropriate. void health\_service\_activities\_iterate([HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) activity\_mask, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthIterationDirection](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthIterationDirection) direction, [HealthActivityIteratorCB](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityIteratorCB) callback, void \* context) Iterates backwards or forward within a given time span to list all recorded activities. For example, this can be used to find the last recorded sleep phase or all deep sleep phases in a given time range. Any activity that overlaps with `time_start` and `time_end` will be included, even if the start time starts before `time_start` or end time ends after `time_end`. #### Parameters activity\_mask A bitmask containing set of activities you are interested in. time\_start UTC time of the earliest time you are interested in. time\_end UTC time of the latest time you are interested in. direction The direction in which to iterate. callback Developer-supplied callback that is called for each activity iterated over. context Developer-supplied context pointer that is passed to the callback. [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_metric\_accessible([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end) Check if a certain combination of metric and time span is accessible using [health\_service\_sum](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum) by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_sum](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum). Note that this call is the same as calling `health_service_metric_averaged_accessible(metric, time_start, time_end, HealthServiceTimeScopeOnce)` #### Parameters metric The metric to query for data. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. #### Returns A [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) representing the accessible metrics in this time range. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_metric\_averaged\_accessible([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Check if a certain combination of metric, time span, and scope is accessible for calculating summed, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_sum\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged). Note that this call is the same as calling `health_service_metric_aggregate_averaged_accessible(metric, time_start, time_end, HealthAggregationSum, HealthServiceTimeScopeOnce)` #### Parameters metric The metric to query for averaged data. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. #### Returns A \HealthServiceAccessibilityMask value decribing whether averaged data is available. [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_metric\_averaged\_accessible([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Check if a certain combination of metric, time span, and scope is accessible for calculating summed, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_sum\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged). Note that this call is the same as calling `health_service_metric_aggregate_averaged_accessible(metric, time_start, time_end, HealthAggregationSum, HealthServiceTimeScopeOnce)` #### Parameters metric The metric to query for averaged data. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. #### Returns A \HealthServiceAccessibilityMask value decribing whether averaged data is available. [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_metric\_averaged\_accessible([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Check if a certain combination of metric, time span, and scope is accessible for calculating summed, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_sum\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged). Note that this call is the same as calling `health_service_metric_aggregate_averaged_accessible(metric, time_start, time_end, HealthAggregationSum, HealthServiceTimeScopeOnce)` #### Parameters metric The metric to query for averaged data. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. #### Returns A [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) value decribing whether averaged data is available. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_metric\_aggregate\_averaged\_accessible([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthAggregation](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation) aggregation, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Check if a certain combination of metric, time span, aggregation operation, and scope is accessible for calculating aggregated, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_aggregate\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged). #### Parameters metric The metric to query for averaged data. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. aggregation The aggregation to perform scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. #### Returns A \HealthServiceAccessibilityMask value decribing whether averaged data is available. [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_metric\_aggregate\_averaged\_accessible([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthAggregation](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation) aggregation, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Check if a certain combination of metric, time span, aggregation operation, and scope is accessible for calculating aggregated, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_aggregate\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged). #### Parameters metric The metric to query for averaged data. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. aggregation The aggregation to perform scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. #### Returns A \HealthServiceAccessibilityMask value decribing whether averaged data is available. [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_metric\_aggregate\_averaged\_accessible([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end, [HealthAggregation](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation) aggregation, [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) scope) Check if a certain combination of metric, time span, aggregation operation, and scope is accessible for calculating aggregated, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_aggregate\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged). #### Parameters metric The metric to query for averaged data. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. aggregation The aggregation to perform scope [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) value describing how the average should be computed. #### Returns A [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) value decribing whether averaged data is available. [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) health\_service\_any\_activity\_accessible([HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) activity\_mask, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_end) Check if a certain combination of metric, [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) and time span is accessible. Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling any other HealthService APIs that involve the given activities. #### Parameters activity\_mask A bitmask of activities you are interested in. time\_start Earliest UTC time you are interested in. time\_end Latest UTC time you are interested in. #### Returns A [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) representing which of the passed [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) values are available under the given constraints. bool health\_service\_events\_subscribe([HealthEventHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHandler) handler, void \* context) Subscribe to HealthService events. This allocates a cache on the application's heap of up to 2048 bytes that will be de-allocated if you call [health\_service\_events\_unsubscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_unsubscribe). If there's not enough heap available, this function will return `false` and will not subscribe to any events. #### Parameters handler Developer-supplied event handler function. context Developer-supplied context pointer. #### Returns `true` on success, `false` on failure. bool health\_service\_events\_unsubscribe(void) Unsubscribe from HealthService events. #### Returns `true` on success, `false` on failure. bool health\_service\_set\_heart\_rate\_sample\_period([uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) interval\_sec) Set the desired sampling period for heart rate readings. Normally, the system will sample the heart rate using a sampling period that is automatically chosen to provide useful information without undue battery drain (it automatically samples more often during periods of intense activity, and less often when the user is idle). If desired though, an application can request a specific sampling period using this call. The system will use this as a suggestion, but does not guarantee that the requested period will be used. The actual sampling period may be greater or less due to system needs or heart rate sensor reading quality issues. Each time a new heart rate reading becomes available, a `HealthEventHeartRateUpdate` event will be sent to the application's `HealthEventHandler`. The sample period request will remain in effect the entire time the app is running unless it is explicitly cancelled (by calling this method again with 0 as the desired interval). If the app exits without first cancelling the request, it will remain in effect even for a limited time afterwards. To determine how long it will remain active after the app exits, use `health_service_get_heart_rate_sample_period_expiration_sec`. Unless the app explicitly needs to access to historical high-resolution heart rate data, it is best practice to always cancel the sample period request before exiting in order to maximize battery life. Historical heart rate data can be accessed using the `health_service_get_minute_history` call. #### Parameters interval\_sec desired interval between heart rate reading updates. Pass 0 to go back to automatically chosen intervals. #### Returns `true` on success, `false` on failure [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) health\_service\_get\_heart\_rate\_sample\_period\_expiration\_sec(void) Return how long a heart rate sample period request (sent via `health_service_set_heart_rate_sample_period`) will remain active after the app exits. If there is no current request by this app, this call will return 0. #### Returns The number of seconds the heart rate sample period request will remain active after the app exits, or 0 if there is no active request by this app. [HealthMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert) \* health\_service\_register\_metric\_alert([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric, [HealthValue](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthValue) threshold) Register for an alert when a metric crosses the given threshold. When the metric crosses this threshold (either goes above or below it), a HealthEventMetricAlert event will be generated. To cancel this registration, pass the returned [HealthMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert) value to [health\_service\_cancel\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_cancel_metric_alert). The only metric currently supported by this call is HealthMetricHeartRateBPM, but future versions may support additional metrics. To see if a specific metric is supported by this call, use: ``` time\_t now = time(NULL);HealthServiceAccessibilityMask accessible =health\_service\_metric\_aggregate\_averaged\_accessible(metric, now, now, HealthAggregationAvg,HealthServiceTimeScopeOnce);bool alert\_supported = (accessible & HealthServiceAccessibilityMaskAvailable); ``` In the current implementation, only one alert per metric can be registered at a time. Future implementations may support two or more simulataneous alert registrations per metric. To change the alert threshold in the current implementation, cancel the original registration using `health_service_cancel_metric_alert` before registering the new threshold. #### Parameters metric Which [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) to query. threshold The threshold value. #### Returns handle to the alert registration on success, NULL on failure bool health\_service\_cancel\_metric\_alert([HealthMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert) \* alert) Cancel an metric alert previously created with [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert). #### Parameters alert the [HealthMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert) previously returned by [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert) #### Returns `true` on success, `false` on failure [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) health\_service\_get\_minute\_history([HealthMinuteData](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMinuteData) \* minute\_data, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) max\_records, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* time\_start, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* time\_end) Return historical minute data records. This fills in the `minute_data` array parameter with minute by minute statistics of the user's steps, average watch orientation, etc. The data is returned in time order, with the oldest minute data returned at `minute_data[0]`. ##### Note If the return value is zero, `time_start` and `time_end` are meaningless. It's not guaranteed that all records contain valid data, even if the return value is greater than zero. Check `HealthMinuteData.is_invalid` to see if a given record contains valid data. #### Parameters minute\_data Pointer to an array of [HealthMinuteData](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMinuteData) records that will be filled in with the historical minute data. max\_records The maximum number of records the `minute_data` array can hold. time\_start (inout) On entry, the UTC time of the first requested record. On exit, the UTC time of the first second of the first record actually returned. If `time_start` on entry is somewhere in the middle of a minute interval, this function behaves as if the caller passed in the start of that minute. time\_end (inout) On entry, the UTC time of the end of the requested range of records. On exit, the UTC time of the end of the last record actually returned (i.e. start time of last record + 60). If `time_end` on entry is somewhere in the middle of a minute interval, this function behaves as if the caller passed in the end of that minute. #### Returns Actual number of records returned. May be less then the maximum requested. [MeasurementSystem](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystem) health\_service\_get\_measurement\_system\_for\_display([HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) metric) Get the preferred measurement system for a given [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric), if the user has chosen a preferred system and it is applicable to that metric. #### Parameters metric A metric value chosen from [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric). #### Returns A value from [MeasurementSystem](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystem) if applicable, else MeasurementSystemUnknown. ## Data Structure Documentation struct HealthMinuteData Structure representing a single minute data record returned by [health\_service\_get\_minute\_history()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_minute_history). The `orientation` field encodes the angle of the watch in the x-y plane (the "yaw") in the lower 4 bits (360 degrees linearly mapped to 1 of 16 different values) and the angle to the z axis (the "pitch") in the upper 4 bits. The `vmc` value is a measure of the total amount of movement seen by the watch. More vigorous movement yields higher VMC values. #### Data Fields uint8\_t steps Number of steps taken in this minute. uint8\_t orientation Quantized average orientation. [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) vmc Vector Magnitude Counts (vmc). bool is\_invalid `true` if the item doesn't represents actual data and should be ignored. [AmbientLightLevel](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevel) light Instantaneous light level during this minute. uint8\_t paddinguint8\_t heart\_rate\_bpm heart rate in beats per minute uint8\_t reserved Reserved for future use. ## Enum Documentation enum HealthMetric Health metric values used to retrieve health data. For example, using [health\_service\_sum()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum). #### Enumerators HealthMetricStepCount The number of steps counted. HealthMetricActiveSeconds The number of seconds spent active (i.e. not resting). HealthMetricWalkedDistanceMeters The distance walked, in meters. HealthMetricSleepSeconds The number of seconds spent sleeping. HealthMetricSleepRestfulSeconds The number of sleep seconds in the 'restful' or deep sleep state. HealthMetricRestingKCalories The number of kcal (Calories) burned while resting due to resting metabolism. HealthMetricActiveKCalories The number of kcal (Calories) burned while active. HealthMetricHeartRateBPM The heart rate, in beats per minute. This is a filtered value that is at most 15 minutes old. HealthMetricHeartRateRawBPM The raw heart rate value of the most recent sample, in beats per minute. enum HealthServiceTimeScope Used by [health\_service\_sum\_averaged()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged) to specify how the average is computed. #### Enumerators HealthServiceTimeScopeOnce No average computed. The result is the same as calling [health\_service\_sum()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum). HealthServiceTimeScopeWeekly Compute average using the same day from each week. For example, every Monday if the passed in time range falls on a Monday. HealthServiceTimeScopeDailyWeekdayOrWeekend Compute average using either weekdays (Monday to Friday) or weekends (Saturday and Sunday), depending on which day the passed in time range falls. HealthServiceTimeScopeDaily Compute average across all days of the week. enum HealthAggregation Used by [health\_service\_aggregate\_averaged()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged) to specify what type of aggregation to perform. This aggregation is applied to the metric before the average is computed. #### Enumerators HealthAggregationSum Sum the metric. The result is the same as calling [health\_service\_sum\_averaged()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged). This operation is only applicable for metrics that accumulate, like HealthMetricStepCount, HealthMetricActiveSeconds, etc. HealthAggregationAvg Use the average of the metric. This is only applicable for metrics that measure instantaneous values, like HealthMetricHeartRateBPM. HealthAggregationMin Use the minimum value of the metric. This is only applicable for metrics that measure instantaneous values, like HealthMetricHeartRateBPM. HealthAggregationMax Use the maximum value of the metric. This is only applicable for metrics that measure instantaneous values, like HealthMetricHeartRateBPM. enum HealthActivity Health-related activities that can be accessed using. #### Enumerators HealthActivityNone No special activity. HealthActivitySleep The 'sleeping' activity. HealthActivityRestfulSleep The 'restful sleeping' activity. HealthActivityWalk The 'walk' activity. HealthActivityRun The 'run' activity. HealthActivityOpenWorkout The 'generic' activity. enum HealthIterationDirection Iteration direction, passed to [health\_service\_activities\_iterate()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_activities_iterate). When iterating backwards (`HealthIterationDirectionPast`), activities that have a greater value for `time_end` come first. When iterating forward (`HealthIterationDirectionFuture`), activities that have a smaller value for `time_start` come first. #### Enumerators HealthIterationDirectionPast Iterate into the past. HealthIterationDirectionFuture Iterate into the future. enum HealthServiceAccessibilityMask Possible values returned by [health\_service\_metric\_accessible()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible). The values are used in combination as a bitmask. For example, to check if any data is available for a given request use: bool any\_data\_available = value & HealthServiceAccessibilityMaskAvailable;. #### Enumerators HealthServiceAccessibilityMaskAvailable Return values are available and represent the collected health information. HealthServiceAccessibilityMaskNoPermission The user hasn't granted permission. HealthServiceAccessibilityMaskNotSupported The queried combination of time span and [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) or [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) is currently unsupported. HealthServiceAccessibilityMaskNotAvailable No samples were recorded for the given time span. enum HealthEventType Health event enum. Passed into the [HealthEventHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHandler). #### Enumerators HealthEventSignificantUpdate All data is considered as outdated and apps should re-read all health data. This happens after an app is subscribed via [health\_service\_events\_subscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_subscribe), on a change of the day, or in other cases that significantly change the underlying data. HealthEventMovementUpdate Recent values around HealthMetricStepCount, HealthMetricActiveSeconds, or HealthMetricWalkedDistanceMeters have changed. HealthEventSleepUpdate Recent values around HealthMetricSleepSeconds, HealthMetricSleepRestfulSeconds, HealthActivitySleep, and HealthActivityRestfulSleep changed. HealthEventMetricAlert A metric has crossed the threshold set by [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert). HealthEventHeartRateUpdate Value of HealthMetricHeartRateBPM or HealthMetricHeartRateRawBPM has changed. HealthEventHRVUpdate A new HRV peak-to-peak interval reading is available. enum AmbientLightLevel Light level enum. #### Enumerators AmbientLightLevelUnknownAmbientLightLevelVeryDarkAmbientLightLevelDarkAmbientLightLevelLightAmbientLightLevelVeryLight enum MeasurementSystem Types of measurement system a [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) may be measured in. #### Enumerators MeasurementSystemUnknown The measurement system is unknown, or does not apply to the chosen metric. MeasurementSystemMetric The metric measurement system. MeasurementSystemImperial The imperial measurement system. ## Typedef Documentation typedef int32\_t HealthValue Type used to represent [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) values. typedef struct [HealthMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert) HealthMetricAlert Type used as a handle to a registered metric alert (returned by [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert)) typedef [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) HealthActivityMask Expresses a set of [HealthActivity](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivity) values as a bitmask. typedef bool(\* HealthActivityIteratorCB)(HealthActivity activity, time\_t time\_start, time\_t time\_end, void \*context) Callback used by [health\_service\_activities\_iterate()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_activities_iterate). #### Parameters activity Which activity the caller is being informed about. time\_start Start UTC time of the activity. time\_end End UTC time of the activity. context The `context` parameter initially passed to [health\_service\_activities\_iterate()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_activities_iterate). #### Returns `true` if you are interested in more activities, or `false` to stop iterating. typedef void(\* HealthEventHandler)(HealthEventType event, void \*context) Developer-supplied event handler, called when a health-related event occurs after subscribing via [health\_service\_events\_subscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_subscribe);. #### Parameters event The type of health-related event that occured. context The developer-supplied context pointer. ## Macro Definition Documentation #define HealthActivityMaskAll ((HealthActivityOpenWorkout \<\< 1) - 1) A mask value representing all available activities. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define PBL\_IF\_HEALTH\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expressions depending on health support. On platforms with health support the first expression will be chosen, the second otherwise. #define PBL\_IF\_HEALTH\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expressions depending on health support. On platforms with health support the first expression will be chosen, the second otherwise. #define PBL\_IF\_HEALTH\_ELSE ( if\_true, if\_false) Convenience macro to switch between two expressions depending on health support. On platforms with health support the first expression will be chosen, the second otherwise. --- # Internationalization Source: https://developer.repebble.com/docs/c/Foundation/Internationalization/ Internationalization & Localization APIs ## Function Documentation const char \* i18n\_get\_system\_locale(void) Get the ISO locale name for the language currently set on the watch. ##### Note It is possible for the locale to change while your app is running. And thus, two calls to i18n\_get\_system\_locale may return different values. #### Returns A string containing the ISO locale name (e.g. "fr", "en\_US", ...) --- # Launch Reason Source: https://developer.repebble.com/docs/c/Foundation/Launch_Reason/ API for checking what caused the application to launch. This includes the system, launch by user interaction (User selects the application from the launcher menu), launch by the mobile or a mobile companion application, or launch by a scheduled wakeup event for the specified application. ## Function Documentation [AppLaunchReason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppLaunchReason) launch\_reason(void) Provides the method used to launch the current application. #### Returns The method or reason the current application was launched [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) launch\_get\_args(void) Get the argument passed to the app when it was launched. ##### Note Currently the only way to pass arguments to apps is by using an openWatchApp action on a pin. #### Returns The argument passed to the app, or 0 if the app wasn't launched from a Launch App action [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) launch\_button(void) Get the button id used to launch the app. Only valid if the launch reason is APP\_LAUNCH\_USER or APP\_LAUNCH\_QUICK\_LAUNCH. [AppQuickLaunchAction](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppQuickLaunchAction) launch\_get\_quick\_launch\_action(void) Get the action that was used to quick launch the app. #### Returns The [AppQuickLaunchAction](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppQuickLaunchAction) used to launch the app, or APP\_QUICK\_LAUNCH\_ACTION\_NONE if the app was not launched via Quick Launch. ## Enum Documentation enum AppLaunchReason [AppLaunchReason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppLaunchReason) is used to inform the application about how it was launched. New launch reasons may be added in the future. As a best practice, it is recommended to only handle the cases that the app needs to know about, rather than trying to handle all possible launch reasons. #### Enumerators APP\_LAUNCH\_SYSTEM App launched by the system. APP\_LAUNCH\_USER App launched by user selection in launcher menu. APP\_LAUNCH\_PHONE App launched by mobile or companion app. APP\_LAUNCH\_WAKEUP App launched by wakeup event. APP\_LAUNCH\_WORKER App launched by worker calling [worker\_launch\_app()](https://developer.repebble.com/docs/c/Worker.md#worker_launch_app) APP\_LAUNCH\_QUICK\_LAUNCH App launched by user using quick launch. APP\_LAUNCH\_TIMELINE\_ACTION App launched by user opening it from a pin. APP\_LAUNCH\_SMARTSTRAP App launched by a smartstrap. enum AppQuickLaunchAction Details about how an app was quick launched. Returned by app\_launch\_get\_quick\_launch\_action. #### Enumerators APP\_QUICK\_LAUNCH\_ACTION\_NONE App was not launched via Quick Launch. APP\_QUICK\_LAUNCH\_ACTION\_HOLD User held a single button. APP\_QUICK\_LAUNCH\_ACTION\_TAP User tapped a button (single click) APP\_QUICK\_LAUNCH\_ACTION\_COMBO User held a button combination. --- # Layers Source: https://developer.repebble.com/docs/c/User_Interface/Layers/ User interface layers for displaying graphic components Layers are objects that can be displayed on a Pebble watchapp window, enabling users to see visual objects, like text or images. Each layer stores the information about its state necessary to draw or redraw the object that it represents and uses graphics routines along with this state to draw itself when asked. Layers can be used to display various graphics. Layers are the basic building blocks for your application UI. Layers can be nested inside each other. Every window has a root layer which is always the topmost layer. You provide a function that is called to draw the content of the layer when needed; or you can use standard layers that are provided by the system, such as text layer, image layer, menu layer, action bar layer, and so on. The Pebble layer hierarchy is the list of things that need to be drawn to the screen. Multiple layers can be arranged into a hierarchy. This enables ordering (front to back), layout and hierarchy. Through relative positioning, visual objects that are grouped together by adding them into the same layer can be moved all at once. This means that the child layers will move accordingly. If a parent layer has clipping enabled, all the children will be clipped to the frame of the parent. Pebble OS provides convenience layers with built-in logic for displaying different graphic components, like text and bitmap layers. Refer to the [User Interface Layers chapter in the Pebble Developer Guides](https://developer.repebble.com/guides/user-interfaces/layers.md) (chapter "Layers") for a conceptual overview of Layers and relevant code examples. The Modules listed here contain what can be thought of conceptually as subclasses of Layer. The listed types can be safely type-casted to `Layer` (or `Layer *` in case of a pointer). The `layer_...` functions can then be used with the data structures of these subclasses. For example, the following is legal: ``` TextLayer \*text\_layer;...layer\_set\_hidden((Layer \*)text\_layer, true); ``` ## Modules #### [ActionBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md) Vertical, bar-shaped control widget on the right edge of the window #### [BitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md) Layer that displays a bitmap image. #### [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) Layer that displays a standard list menu. Data is provided using callbacks. #### [RotBitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md) Layer that displays a rotated bitmap image. #### [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md) Layer that scrolls its contents, animated. #### [SimpleMenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md) Wrapper around [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), that uses static data to display a list menu. #### [StatusBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md) Layer that serves as a configurable status bar. #### [TextLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md) Layer that displays and formats a text string. ## Function Documentation Layer \* layer\_create([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame) Creates a layer on the heap and sets its frame and bounds. Default values: - `bounds` : origin (0, 0) and a size equal to the frame that is passed in. - `clips` : `true` - `hidden` : `false` - `update_proc` : `NULL` (draws nothing) #### Parameters frame The frame at which the layer should be initialized. #### Returns A pointer to the layer. `NULL` if the layer could not be created #### See Also [layer\_set\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) [layer\_set\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) Layer \* layer\_create\_with\_data([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) data\_size) Creates a layer on the heap with extra space for callback data, and set its frame andbounds. Default values: - `bounds` : origin (0, 0) and a size equal to the frame that is passed in. - `clips` : `true` - `hidden` : `false` - `update_proc` : `NULL` (draws nothing) #### Parameters frame The frame at which the layer should be initialized. data\_size The size (in bytes) of memory to allocate for callback data. #### Returns A pointer to the layer. `NULL` if the layer could not be created #### See Also [layer\_create()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_create) [layer\_set\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) [layer\_set\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) void layer\_destroy(Layer \* layer) Destroys a layer previously created by layer\_create. void layer\_mark\_dirty(Layer \* layer) Marks the complete layer as "dirty", awaiting to be asked by the system to redraw itself. Typically, this function is called whenever state has changed that affects what the layer is displaying. - The layer's `.update_proc` will not be called before this function returns, but will be called asynchronously, shortly. - Internally, a call to this function will schedule a re-render of the window that the layer belongs to. In effect, all layers in that window's layer hierarchy will be asked to redraw. - If an earlier re-render request is still pending, this function is a no-op. #### Parameters layer The layer to mark dirty void layer\_set\_update\_proc(Layer \* layer, [LayerUpdateProc](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc) update\_proc) Sets the layer's render function. The system will call the `update_proc` automatically when the layer needs to redraw itself, see also [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty). #### Parameters layer Pointer to the layer structure. update\_proc Pointer to the function that will be called when the layer needs to be rendered. Typically, one performs a series of drawing commands in the implementation of the `update_proc`, see [Drawing Primitives](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md), [Drawing Paths](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md) and [Drawing Text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md). void layer\_set\_frame(Layer \* layer, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame) Sets the frame of the layer, which is it's bounding box relative to the coordinate system of its parent layer. The size of the layer's bounds will be extended automatically, so that the bounds cover the new frame. #### Parameters layer The layer for which to set the frame frame The new frame #### See Also [layer\_set\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) layer\_get\_frame(const Layer \* layer) Gets the frame of the layer, which is it's bounding box relative to the coordinate system of its parent layer. If the frame has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically. #### Parameters layer The layer for which to get the frame #### Returns The frame of the layer #### See Also [layer\_set\_frame](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) void layer\_set\_bounds(Layer \* layer, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) bounds) Sets the bounds of the layer, which is it's bounding box relative to its frame. If the bounds has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically. #### Parameters layer The layer for which to set the bounds bounds The new bounds #### See Also [layer\_set\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) layer\_get\_bounds(const Layer \* layer) Gets the bounds of the layer. #### Parameters layer The layer for which to get the bounds #### Returns The bounds of the layer #### See Also [layer\_set\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) layer\_get\_unobstructed\_bounds(const Layer \* layer) Get the largest unobstructed bounds rectangle of a layer. #### Parameters layer The layer for which to get the unobstructed bounds. #### Returns The unobstructed bounds of the layer. #### See Also [UnobstructedArea](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md) [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) layer\_convert\_point\_to\_screen(const Layer \* layer, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) point) Converts a point from the layer's local coordinate system to screen coordinates. ##### Note If the layer isn't part of the view hierarchy the result is undefined. #### Parameters layer The view whose coordinate system will be used to convert the value to the screen. point A point specified in the local coordinate system (bounds) of the layer. #### Returns The point converted to the coordinate system of the screen. [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) layer\_convert\_rect\_to\_screen(const Layer \* layer, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) rect) Converts a rectangle from the layer's local coordinate system to screen coordinates. ##### Note If the layer isn't part of the view hierarchy the result is undefined. #### Parameters layer The view whose coordinate system will be used to convert the value to the screen. rect A rectangle specified in the local coordinate system (bounds) of the layer. #### Returns The rectangle converted to the coordinate system of the screen. struct Window \* layer\_get\_window(const Layer \* layer) Gets the window that the layer is currently attached to. #### Parameters layer The layer for which to get the window #### Returns The window that this layer is currently attached to, or `NULL` if it has not been added to a window's layer hierarchy. #### See Also [window\_get\_root\_layer()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_root_layer) [layer\_add\_child()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_add_child) void layer\_remove\_from\_parent(Layer \* child) Removes the layer from its current parent layer If removed successfully, the child's parent layer will be marked dirty automatically. #### Parameters child The layer to remove void layer\_remove\_child\_layers(Layer \* parent) Removes child layers from given layer If removed successfully, the child's parent layer will be marked dirty automatically. #### Parameters parent The layer from which to remove all child layers void layer\_add\_child(Layer \* parent, Layer \* child) Adds the child layer to a given parent layer, making it appear in front of its parent and in front of any existing child layers of the parent. If the child layer was already part of a layer hierarchy, it will be removed from its old parent first. If added successfully, the parent (and children) will be marked dirty automatically. #### Parameters parent The layer to which to add the child layer child The layer to add to the parent layer void layer\_insert\_below\_sibling(Layer \* layer\_to\_insert, Layer \* below\_sibling\_layer) Inserts the layer as a sibling behind another layer. If the layer to insert was already part of a layer hierarchy, it will be removed from its old parent first. The below\_layer has to be a child of a parent layer, otherwise this function will be a noop. If inserted successfully, the parent (and children) will be marked dirty automatically. #### Parameters layer\_to\_insert The layer to insert into the hierarchy below\_sibling\_layer The layer that will be used as the sibling layer above which the insertion will take place void layer\_insert\_above\_sibling(Layer \* layer\_to\_insert, Layer \* above\_sibling\_layer) Inserts the layer as a sibling in front of another layer. The above\_layer has to be a child of a parent layer, otherwise this function will be a noop. If inserted successfully, the parent (and children) will be marked dirty automatically. #### Parameters layer\_to\_insert The layer to insert into the hierarchy above\_sibling\_layer The layer that will be used as the sibling layer below which the insertion will take place void layer\_set\_hidden(Layer \* layer, bool hidden) Sets the visibility of the layer. If the visibility has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically on the parent layer. #### Parameters layer The layer for which to set the visibility hidden Supply `true` to make the layer hidden, or `false` to make it non-hidden. bool layer\_get\_hidden(const Layer \* layer) Gets the visibility of the layer. #### Parameters layer The layer for which to get the visibility #### Returns True if the layer is hidden, false if it is not hidden. void layer\_set\_clips(Layer \* layer, bool clips) Sets whether clipping is enabled for the layer. If enabled, whatever the layer _and its children_ will draw using their `.update_proc` callbacks, will be clipped by the this layer's frame. If the clipping has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically. #### Parameters layer The layer for which to set the clipping property clips Supply `true` to make the layer clip to its frame, or `false` to make it non-clipping. bool layer\_get\_clips(const Layer \* layer) Gets whether clipping is enabled for the layer. If enabled, whatever the layer _and its children_ will draw using their `.update_proc` callbacks, will be clipped by the this layer's frame. #### Parameters layer The layer for which to get the clipping property #### Returns True if clipping is enabled for the layer, false if clipping is not enabled for the layer. void \* layer\_get\_data(const Layer \* layer) Gets the data from a layer that has been created with an extra data region. #### Parameters layer The layer to get the data region from. #### Returns A void pointer to the data region. ## Typedef Documentation typedef struct Layer Layer typedef void(\* LayerUpdateProc)(struct Layer \*layer, GContext \*ctx) Function signature for a Layer's render callback (the name of the type is derived from the words 'update procedure'). The system will call the `.update_proc` callback whenever the Layer needs to be rendered. #### Parameters layer The layer that needs to be rendered ctx The destination graphics context to draw into #### See Also [Graphics](https://developer.repebble.com/docs/c/Graphics.md) [layer\_set\_update\_proc()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_update_proc) --- # Light Source: https://developer.repebble.com/docs/c/User_Interface/Light/ Controlling Pebble's backlight The Light API provides you with functions to turn on Pebble’s backlight or put it back into automatic control. You can trigger the backlight and schedule a timer to automatically disable the backlight after a short delay, which is the preferred method of interacting with the backlight. ## Function Documentation void light\_enable\_interaction(void) Trigger the backlight and schedule a timer to automatically disable the backlight after a short delay. This is the preferred method of interacting with the backlight. void light\_enable(bool enable) Turn the watch's backlight on or put it back into automatic control. Developers should take care when calling this function, keeping Pebble's backlight on for long periods of time will rapidly deplete the battery. #### Parameters enable Turn the backlight on if `true`, otherwise `false` to put it back into automatic control. bool light\_is\_on(void) #### Returns true if the backlight is currently on in any form (on, timed, or fading out). Returns false only when the backlight is fully off. Useful for apps that want to behave differently depending on whether the screen is currently lit — e.g. skipping an animation or queuing a visual cue for when the screen wakes. void light\_set\_color(GColor color) Tint the backlight LED to the given color. The color persists while the app is foregrounded and is automatically reset to the user's default (white) when the app exits or is preempted by a system notification. On platforms without a color backlight this is a no-op. ##### Note GColor carries only 2 bits per channel (64 distinct tints). Use [light\_set\_color\_rgb888()](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_color_rgb888) if you need the full 8-bit-per-channel range that the LED hardware supports. #### Parameters color The color to tint the backlight to. void light\_set\_color\_rgb888([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) rgb) Tint the backlight LED to a packed 24-bit RGB value (0x00RRGGBB). Same persistence semantics as [light\_set\_color()](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_color): the override lasts while the app is foregrounded and is reset on app exit or system preempt. No-op on platforms without a color backlight. #### Parameters rgb Packed 0x00RRGGBB value; 8 bits per channel. High byte ignored. void light\_set\_system\_color(void) Restore the backlight to the user's default color. Rarely needed — the system resets automatically on app exit. No-op on platforms without a color backlight. --- # Locale Source: https://developer.repebble.com/docs/c/Standard_C/Locale/ Standard locale functions. ## Function Documentation char \* setlocale(int category, char \* locale) Set the app's locale for a category of routines. `setlocale` can be used to: - set the app's locale to a specific locale: `setlocale(LC_ALL, "en_CA")` - set the app's locale to the system locale: `setlocale(LC_ALL, "")` - get the app's curent locale: `setlocale(LC_ALL, NULL)` ##### Note Currently, we only support two categories: LC\_ALL and LC\_TIME #### Parameters category The category of routines for which to set the locale locale The ISO formatted locale to use, or "" for the system locale #### Returns the locale after the change is applied, NULL on failure (e.g. unsuported category) --- # Logging Source: https://developer.repebble.com/docs/c/Foundation/Logging/ Functions related to logging from apps. This module contains the functions necessary to log messages through Bluetooth. ## Function Documentation void app\_log(uint8\_t log\_level, const char \* src\_filename, int src\_line\_number, const char \* fmt, ...) ## Enum Documentation enum AppLogLevel Suggested log level values. #### Enumerators APP\_LOG\_LEVEL\_ERROR Error level log message. APP\_LOG\_LEVEL\_WARNING Warning level log message. APP\_LOG\_LEVEL\_INFO Info level log message. APP\_LOG\_LEVEL\_DEBUG Debug level log message. APP\_LOG\_LEVEL\_DEBUG\_VERBOSE Verbose Debug level log message. ## Macro Definition Documentation #define APP\_LOG ( level, fmt, args...) A helper macro that simplifies the use of the app\_log function. #### Parameters level The log level to log output as fmt A C formatting string args The arguments for the formatting string --- # Math Source: https://developer.repebble.com/docs/c/Foundation/Math/ Math routines. Below is a code example that uses the trigonometry functions to calculate the coordinate at which the second hand of a watch ends, using seconds from the system time. ``` GPoint secondHand;GPoint center;struct tm \*tick\_time = ...;int32\_t secondHandLength = ...;...int32\_t second\_angle = TRIG\_MAX\_ANGLE \* tick\_time-\>tm\_sec / 60;secondHand.y = (-cos\_lookup(second\_angle) \* secondHandLength / TRIG\_MAX\_RATIO) + center.y;secondHand.x = (sin\_lookup(second\_angle) \* secondHandLength / TRIG\_MAX\_RATIO) + center.x; ``` ## Function Documentation int32\_t sin\_lookup(int32\_t angle) Look-up the sine of the given angle from a pre-computed table. #### Parameters angle The angle for which to compute the cosine. The angle value is scaled linearly, such that a value of 0x10000 corresponds to 360 degrees or 2 PI radians. int32\_t cos\_lookup(int32\_t angle) Look-up the cosine of the given angle from a pre-computed table. This is equivalent to calling `sin_lookup(angle + TRIG_MAX_ANGLE / 4)`. #### Parameters angle The angle for which to compute the cosine. The angle value is scaled linearly, such that a value of 0x10000 corresponds to 360 degrees or 2 PI radians. int32\_t atan2\_lookup(int16\_t y, int16\_t x) Look-up the arctangent of a given x, y pair The angle value is scaled linearly, such that a value of 0x10000 corresponds to 360 degrees or 2 PI radians. ## Macro Definition Documentation #define TRIG\_MAX\_RATIO 0xffff The largest value that can result from a call to [sin\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#sin_lookup) or [cos\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#cos_lookup). For a code example, see the detailed description at the top of this chapter: [Math](https://developer.repebble.com/docs/c/Foundation/Math.md). #define TRIG\_MAX\_ANGLE 0x10000 Angle value that corresponds to 360 degrees or 2 PI radians. #### See Also [sin\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#sin_lookup) [cos\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#cos_lookup) #define TRIGANGLE\_TO\_DEG ( trig\_angle) Converts from a fixed point value representation to the equivalent value in degrees. #### See Also [DEG\_TO\_TRIGANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE) [TRIG\_MAX\_ANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_ANGLE) #define DEG\_TO\_TRIGANGLE ( angle) Converts from an angle in degrees to the equivalent fixed point value representation. #### See Also [TRIGANGLE\_TO\_DEG](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIGANGLE_TO_DEG) [TRIG\_MAX\_ANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_ANGLE) --- # Math Source: https://developer.repebble.com/docs/c/Standard_C/Math/ Standard math functions. ## Function Documentation int rand() Generate a pseudo-random integer between 0 and [RAND\_MAX](https://developer.repebble.com/docs/c/Standard_C/Math.md#RAND_MAX) inclusive. This function can be seeded by [srand()](https://developer.repebble.com/docs/c/Standard_C/Math.md#srand). A simple way to change the range to be an integer between 0 and n-1 inclusive is using % e.g. `rand() % n`. #### Returns The pseudo-randomly generated number void srand(unsigned int seed) Seed the pseudo-random number generator. When your app starts, the pseudo-random number generator is automatically seeded with a random number that is generated by a high-entropy hardware random number generator. This function affects subsequent calls to [rand()](https://developer.repebble.com/docs/c/Standard_C/Math.md#rand) to produce a sequence of numbers for a given seed value. You can use this to either create a different sequence of numbers by always using a different seed or to obtain the same sequence of numbers by reusing the same seed. #### Parameters seed The source number used to generate a sequence of pseudo-random numbers ## Macro Definition Documentation #define RAND\_MAX The maximum integer value [rand()](https://developer.repebble.com/docs/c/Standard_C/Math.md#rand) may return. --- # Memory Source: https://developer.repebble.com/docs/c/Standard_C/Memory/ Standard memory functions. ## Function Documentation void \* malloc([size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) size) Allocates a requested amount of memory. #### Parameters size The number of bytes to allocate #### Returns A pointer to the allocated memory or NULL on error. void \* calloc([size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) count, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) size) Allocates space for count objects that are size bytes and fills the memory with bytes of value 0. #### Parameters count The number of objects to allocate space for size The size of the object type being allocated #### Returns A pointer to the allocated memory or NULL on error. void \* realloc(void \* ptr, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) size) Takes the memory allocated at ptr and changes the length of its allocation to the size specified. Copies the smaller of the length of the original allocation or the new size into the newly allocated buffer. #### Parameters ptr The memory allocation to be changed size The size to change the ptr allocation to #### Returns A pointer to the new allocated memory or NULL on error void free(void \* ptr) Frees previously allocated memory. #### Parameters ptr The memory buffer to free. int memcmp(const void \* ptr1, const void \* ptr2, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n) Compares the first n bytes of memory regions ptr1 and ptr2. #### Parameters ptr1 The pointer to the first memory region to compare ptr2 The pointer to the second memory region to compare n The number of bytes to compare #### Returns 0 if the first n bytes of ptr1 and ptr 2 match. Otherwise, the sign is determined by the sign of the difference between the first pair of bytes that differ in ptr1 and ptr2. void \* memcpy(void \* dest, const void \* src, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n) Copies n bytes from src to dest. #### Parameters dest The pointer to the destination memory region src The pointer to the source memory region n The number of bytes to copy void \* memmove(void \* dest, const void \* src, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n) Copies n bytes from src to dest by first copying to a temporary area first, allowing dest and src to potentially overlap. This can be used to move data to a location that overlaps its previous location. #### Parameters dest The pointer to the destination memory region src The pointer to the source memory region n The number of bytes to copy void \* memset(void \* dest, int c, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n) Sets n bytes to c starting at dest. This can be used to clear a memory region for example if c is 0. #### Parameters dest The pointer to the destination memory region c The integer used as an unsigned char to assign to each byte n The number of bytes to set ## Typedef Documentation typedef unsigned int size\_t size as an unsigned integer --- # Memory Management Source: https://developer.repebble.com/docs/c/Foundation/Memory_Management/ Utility functions for managing an application's memory. ## Function Documentation [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) heap\_bytes\_free(void) Calculates the number of bytes of heap memory _not_ currently being used by the application. #### Returns The number of bytes on the heap not currently being used. [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) heap\_bytes\_used(void) Calculates the number of bytes of heap memory currently being used by the application. #### Returns The number of bytes on the heap currently being used. void memory\_cache\_flush(void \* start, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) size) Flushes the data cache and invalidates the instruction cache for the given region of memory, if necessary. This is only required when your app is loading or modifying code in memory and intends to execute it. On some platforms, code executed may be cached internally to improve performance. After writing to memory, but before executing, this function must be called in order to avoid undefined behavior. On platforms without caching, this performs no operation. #### Parameters start The beginning of the buffer to flush size How many bytes to flush --- # MenuLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer/ Layer that displays a standard list menu. Data is provided using callbacks. ![](https://developer.repebble.com/assets/images/docs/c/aplite/menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/menu_layer.png) ### Key Points - The familiar list-style menu widget, as used throughout the Pebble user interface. - Built on top of [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md), inheriting all its goodness like animated scrolling, automatic "more content" shadow indicators, etc. - All data needed to render the menu is requested on-demand via callbacks, to avoid the need to keep a lot of data in memory. - Support for "sections". A section is a group of items, visually separated by a header with the name at the top of the section. - Variable heights: each menu item cell and each section header can have its own height. The heights are provided by callbacks. - Deviation from the Layer system for cell drawing: Each menu item does _not_ have its own Layer (to minimize memory usage). Instead, a drawing callback is set onto the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) that is responsible for drawing each menu item. The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) will call this callback for each menu item that is visible and needs to be rendered. - Cell and header drawing can be customized by implementing a custom drawing callback. - A few "canned" menu cell drawing functions are provided for convenience, which support the default menu cell layout with a title, optional subtitle and icon. For short, static list menus, consider using [SimpleMenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md). ## Function Documentation void menu\_cell\_basic\_draw(GContext \* ctx, const Layer \* cell\_layer, const char \* title, const char \* subtitle, GBitmap \* icon) Section drawing function to draw a basic section cell with the title, subtitle, and icon of the section. Call this function inside the `.draw_row` callback implementation, see [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks). Note that if the size of `cell_layer` is too small to fit all of the cell items specified, not all of them may be drawn. #### Parameters ctx The destination graphics context cell\_layer The layer of the cell to draw title If non-null, draws a title in larger text (24 points, bold Raster Gothic system font). subtitle If non-null, draws a subtitle in smaller text (18 points, Raster Gothic system font). If `NULL`, the title will be centered vertically inside the menu cell. icon If non-null, draws an icon to the left of the text. If `NULL`, the icon will be omitted and the leftover space is used for the title and subtitle. void menu\_cell\_title\_draw(GContext \* ctx, const Layer \* cell\_layer, const char \* title) Cell drawing function to draw a basic menu cell layout with title, subtitle Cell drawing function to draw a menu cell layout with only one big title. Call this function inside the `.draw_row` callback implementation, see [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks). #### Parameters ctx The destination graphics context cell\_layer The layer of the cell to draw title If non-null, draws a title in larger text (28 points, bold Raster Gothic system font). void menu\_cell\_basic\_header\_draw(GContext \* ctx, const Layer \* cell\_layer, const char \* title) Section header drawing function to draw a basic section header cell layout with the title of the section. Call this function inside the `.draw_header` callback implementation, see [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks). #### Parameters ctx The destination graphics context cell\_layer The layer of the cell to draw title If non-null, draws the title in small text (14 points, bold Raster Gothic system font). int16\_t menu\_index\_compare(const [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) \* a, const [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) \* b) Comparator function to determine the order of two [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) values. #### Parameters a Pointer to the menu index of the first item b Pointer to the menu index of the second item #### Returns 0 if A and B are equal, 1 if A has a higher section & row combination than B or else -1 MenuLayer \* menu\_layer\_create([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame) Creates a new [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) on the heap and initalizes it with the default values. - Clips: `true` - Hidden: `false` - Content size: `frame.size` - Content offset: [GPointZero](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPointZero) - Callbacks: None (`NULL` for each one) - Callback context: `NULL` - After the relevant callbacks are called to populate the menu, the item at [MenuIndex(0, 0)](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) will be selected initially. #### Returns A pointer to the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). `NULL` if the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) could not be created void menu\_layer\_destroy(MenuLayer \* menu\_layer) Destroys a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) previously created by menu\_layer\_create. Layer \* menu\_layer\_get\_layer(const MenuLayer \* menu\_layer) Gets the "root" Layer of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), which is the parent for the sub- layers used for its implementation. #### Parameters menu\_layer Pointer to the MenuLayer for which to get the "root" Layer #### Returns The "root" Layer of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). ScrollLayer \* menu\_layer\_get\_scroll\_layer(const MenuLayer \* menu\_layer) Gets the ScrollLayer of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), which is the layer responsible for the scrolling of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). #### Parameters menu\_layer Pointer to the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to get the ScrollLayer #### Returns The ScrollLayer of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). void menu\_layer\_set\_callbacks(MenuLayer \* menu\_layer, void \* callback\_context, [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) callbacks) Sets the callbacks for the MenuLayer. #### Parameters menu\_layer Pointer to the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to set the callbacks and callback context. callback\_context The new callback context. This is passed into each of the callbacks and can be set to point to application provided data. callbacks The new callbacks for the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). The storage for this data structure must be long lived. Therefore, it cannot be stack-allocated. #### See Also [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) void menu\_layer\_set\_click\_config\_onto\_window(MenuLayer \* menu\_layer, struct Window \* window) Convenience function to set the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) callback on the given window to the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) internal click config provider. This internal click configuration provider, will set up the default UP & DOWN scrolling / menu item selection behavior. This function calls [scroll\_layer\_set\_click\_config\_onto\_window](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window) to accomplish this. Click and long click events for the SELECT button can be handled by installing the appropriate callbacks using [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks). This is a deviation from the usual click configuration provider pattern. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) that needs to receive click events. window The window for which to set the click configuration. #### See Also [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) [scroll\_layer\_set\_click\_config\_onto\_window()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window) void menu\_layer\_set\_selected\_next(MenuLayer \* menu\_layer, bool up, [MenuRowAlign](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuRowAlign) scroll\_align, bool animated) Selects the next or previous item, relative to the current selection. ##### Note If there is no next/previous item, this function is a no-op. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to select the next item up Supply `false` to select the next item in the list (downwards), or `true` to select the previous item in the list (upwards). scroll\_align The alignment of the new selection animated Supply `true` to animate changing the selection, or `false` to change the selection instantly. void menu\_layer\_set\_selected\_index(MenuLayer \* menu\_layer, [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) index, [MenuRowAlign](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuRowAlign) scroll\_align, bool animated) Selects the item with given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). ##### Note If the section and/or row index exceeds the avaible number of sections or resp. rows, the exceeding index/indices will be capped, effectively selecting the last section and/or row, resp. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to change the selection index The index of the item to select scroll\_align The alignment of the new selection animated Supply `true` to animate changing the selection, or `false` to change the selection instantly. [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) menu\_layer\_get\_selected\_index(const MenuLayer \* menu\_layer) Gets the [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the currently selected menu item. ##### Note This function should not be used to determine whether a cell should be highlighted or not. See [menu\_cell\_layer\_is\_highlighted](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_layer_is_highlighted) for more information. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to get the current selected index. #### See Also [menu\_cell\_layer\_is\_highlighted](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_layer_is_highlighted) void menu\_layer\_reload\_data(MenuLayer \* menu\_layer) Reloads the data of the menu. This causes the menu to re-request the menu item data, by calling the relevant callbacks. The current selection and scroll position will not be changed. See the note with [menu\_layer\_set\_selected\_index()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_selected_index) for the behavior if the old selection is no longer valid. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to reload the data. bool menu\_cell\_layer\_is\_highlighted(const Layer \* cell\_layer) Returns whether or not the given cell layer is highlighted. Using this for determining highlight behaviour is preferable to using [menu\_layer\_get\_selected\_index](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_get_selected_index). Row drawing callbacks may be invoked multiple times with a different highlight status on the same cell in order to handle partially highlighted cells during animation. #### Parameters cell\_layer The [Layers](https://developer.repebble.com/docs/c/User_Interface/Layers.md) for the cell to check highlight status. #### Returns true if the given cell layer is highlighted in the menu. void menu\_layer\_set\_normal\_colors(MenuLayer \* menu\_layer, GColor background, GColor foreground) Set the default colors to be used for cells when it is in a normal state (not highlighted). The GContext's text and fill colors will be set appropriately prior to calling the `.draw_row` callback. If this function is not explicitly called on a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), it will default to white background with black foreground. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to set the colors. background The color to be used for the background of the cell. foreground The color to be used for the foreground and text of the cell. #### See Also [menu\_layer\_set\_highlight\_colors](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_highlight_colors) void menu\_layer\_set\_highlight\_colors(MenuLayer \* menu\_layer, GColor background, GColor foreground) Set the default colors to be used for cells when it is in a highlighted state. The GContext's text and fill colors will be set appropriately prior to calling the `.draw_row` callback. If this function is not explicitly called on a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), it will default to black background with white foreground. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which to set the colors. background The color to be used for the background of the cell. foreground The color to be used for the foreground and text of the cell. #### See Also [menu\_layer\_set\_normal\_colors](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_normal_colors) void menu\_layer\_pad\_bottom\_enable(MenuLayer \* menu\_layer, bool enable) This enables or disables padding at the bottom of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). Padding at the bottom of the layer keeps the bottom item from being at the very bottom of the screen. Padding is turned on by default for all MenuLayers. The color of the padded area will be the background color set using [menu\_layer\_set\_normal\_colors()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_normal_colors). To color the padding a different color, use [MenuLayerDrawBackgroundCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawBackgroundCallback). #### Parameters menu\_layer The menu layer for which to enable or disable the padding. enable True = enable padding, False = disable padding. bool menu\_layer\_get\_center\_focused(MenuLayer \* menu\_layer) True, if the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) generally scrolls such that the selected row is in the center. #### See Also [menu\_layer\_set\_center\_focused](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_center_focused) void menu\_layer\_set\_center\_focused(MenuLayer \* menu\_layer, bool center\_focused) Controls if the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) generally scrolls such that the selected row is in the center. For platforms with a round display (PBL\_ROUND) the default is true, otherwise false is the default. #### Parameters menu\_layer The menu layer for which to enable or disable the behavior. center\_focused true = enable the mode, false = disable it. #### See Also [menu\_layer\_get\_center\_focused](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_get_center_focused) bool menu\_layer\_is\_index\_selected(const MenuLayer \* menu\_layer, [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) \* index) Returns whether or not the specified cell index is currently selected. ##### Note This function should not be used to determine whether a cell is highlighted or not. See [menu\_cell\_layer\_is\_highlighted](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_layer_is_highlighted) for more information. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) to use when determining if the index is selected. index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the cell to check for selection. ## Data Structure Documentation struct MenuCellSpan #### Data Fields int16\_t yint16\_t hint16\_t sep[MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) index struct MenuIndex Data structure to represent an menu item's position in a menu, by specifying the section index and the row index within that section. #### Data Fields [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) section The index of the section. [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) row The index of the row within the section with index `.section` struct MenuLayerCallbacks Data structure containing all the callbacks of a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). #### Data Fields [MenuLayerGetNumberOfSectionsCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetNumberOfSectionsCallback) get\_num\_sections Callback that gets called to get the number of sections in the menu. This can get called at various moments throughout the life of a menu. ##### Note When `NULL`, the number of sections defaults to 1. [MenuLayerGetNumberOfRowsInSectionsCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetNumberOfRowsInSectionsCallback) get\_num\_rows Callback that gets called to get the number of rows in a section. This can get called at various moments throughout the life of a menu. ##### Note Must be set to a valid callback; `NULL` causes undefined behavior. [MenuLayerGetCellHeightCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetCellHeightCallback) get\_cell\_height Callback that gets called to get the height of a cell. This can get called at various moments throughout the life of a menu. ##### Note When `NULL`, the default height of `MENU_CELL_BASIC_CELL_HEIGHT` pixels is used. Developers may wish to use [MENU\_CELL\_ROUND\_FOCUSED\_SHORT\_CELL\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT) and [MENU\_CELL\_ROUND\_UNFOCUSED\_SHORT\_CELL\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT) on a round display to respect the system aesthetic. [MenuLayerGetHeaderHeightCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetHeaderHeightCallback) get\_header\_height Callback that gets called to get the height of a section header. This can get called at various moments throughout the life of a menu. ##### Note When `NULL`, the default height of 0 pixels is used. This disables section headers. [MenuLayerDrawRowCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawRowCallback) draw\_row Callback that gets called to render a menu item. This gets called for each menu item, every time it needs to be re-rendered. ##### Note Must be set to a valid callback; `NULL` causes undefined behavior. [MenuLayerDrawHeaderCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawHeaderCallback) draw\_header Callback that gets called to render a section header. This gets called for each section header, every time it needs to be re-rendered. ##### Note Must be set to a valid callback, unless `.get_header_height` is `NULL`. Causes undefined behavior otherwise. [MenuLayerSelectCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerSelectCallback) select\_click Callback that gets called when the user triggers a click with the SELECT button. ##### Note When `NULL`, click events for the SELECT button are ignored. [MenuLayerSelectCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerSelectCallback) select\_long\_click Callback that gets called when the user triggers a long click with the SELECT button. ##### Note When `NULL`, long click events for the SELECT button are ignored. [MenuLayerSelectionChangedCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerSelectionChangedCallback) selection\_changed Callback that gets called whenever the selection changes. ##### Note When `NULL`, selection change events are ignored. [MenuLayerGetSeparatorHeightCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetSeparatorHeightCallback) get\_separator\_height Callback that gets called to get the height of a separator This can get called at various moments throughout the life of a menu. ##### Note When `NULL`, the default height of 0 is used. [MenuLayerDrawSeparatorCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawSeparatorCallback) draw\_separator Callback that gets called to render a separator. This gets called for each separator, every time it needs to be re-rendered. ##### Note Must be set to a valid callback, unless `.get_separator_height` is `NULL`. Causes undefined behavior otherwise. [MenuLayerSelectionWillChangeCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerSelectionWillChangeCallback) selection\_will\_change Callback that gets called before the selected cell changes. This gets called before the selected item in the MenuLayer is changed, and will allow for the selected cell to be overridden. This allows for skipping cells in the menu, locking selection onto a given item,. [MenuLayerDrawBackgroundCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawBackgroundCallback) draw\_background Callback that gets called before any cells are drawn. This supports two states, either highlighted or not highlighted. If highlighted is specified, it is expected to be colored in the same style as the menu's cells are. If this callback is not specified, it will default to the colors set with [menu\_layer\_set\_normal\_colors](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_normal_colors) and [menu\_layer\_set\_highlight\_colors](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_highlight_colors). ## Enum Documentation enum MenuRowAlign Values to specify how a (selected) row should be aligned relative to the visible area of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). #### Enumerators MenuRowAlignNone Don't align or update the scroll offset of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). MenuRowAlignCenter Scroll the contents of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) in such way that the selected row is centered relative to the visible area. MenuRowAlignTop Scroll the contents of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) in such way that the selected row is at the top of the visible area. MenuRowAlignBottom Scroll the contents of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) in such way that the selected row is at the bottom of the visible area. ## Typedef Documentation typedef struct MenuLayer MenuLayer typedef [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t)(\* MenuLayerGetNumberOfSectionsCallback)(struct MenuLayer \*menu\_layer, void \*callback\_context) Function signature for the callback to get the number of sections in a menu. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the data is requested callback\_context The callback context #### Returns The number of sections in the menu #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t)(\* MenuLayerGetNumberOfRowsInSectionsCallback)(struct MenuLayer \*menu\_layer, uint16\_t section\_index, void \*callback\_context) Function signature for the callback to get the number of rows in a given section in a menu. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the data is requested section\_index The index of the section of the menu for which the number of items it contains is requested callback\_context The callback context #### Returns The number of rows in the given section in the menu #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef int16\_t(\* MenuLayerGetCellHeightCallback)(struct MenuLayer \*menu\_layer, MenuIndex \*cell\_index, void \*callback\_context) Function signature for the callback to get the height of the menu cell at a given index. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the data is requested cell\_index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) for which the cell height is requested callback\_context The callback context #### Returns The height of the cell at the given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef int16\_t(\* MenuLayerGetHeaderHeightCallback)(struct MenuLayer \*menu\_layer, uint16\_t section\_index, void \*callback\_context) Function signature for the callback to get the height of the section header at a given section index. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the data is requested section\_index The index of the section for which the header height is requested callback\_context The callback context #### Returns The height of the section header at the given section index #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef int16\_t(\* MenuLayerGetSeparatorHeightCallback)(struct MenuLayer \*menu\_layer, MenuIndex \*cell\_index, void \*callback\_context) Function signature for the callback to get the height of the separator at a given index. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the data is requested cell\_index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) for which the cell height is requested callback\_context The callback context #### Returns The height of the separator at the given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef void(\* MenuLayerDrawRowCallback)(GContext \*ctx, const Layer \*cell\_layer, MenuIndex \*cell\_index, void \*callback\_context) Function signature for the callback to render the menu cell at a given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). ##### Note The `cell_layer` argument is provided to make it easy to re-use an `.update_proc` implementation in this callback. Only the bounds and frame of the `cell_layer` are actually valid and other properties should be ignored. #### Parameters ctx The destination graphics context to draw into cell\_layer The cell's layer, containing the geometry of the cell cell\_index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the cell that needs to be drawn callback\_context The callback context #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef void(\* MenuLayerDrawHeaderCallback)(GContext \*ctx, const Layer \*cell\_layer, uint16\_t section\_index, void \*callback\_context) Function signature for the callback to render the section header at a given section index. ##### Note The `cell_layer` argument is provided to make it easy to re-use an `.update_proc` implementation in this callback. Only the bounds and frame of the `cell_layer` are actually valid and other properties should be ignored. #### Parameters ctx The destination graphics context to draw into cell\_layer The header cell's layer, containing the geometry of the header cell section\_index The section index of the section header that needs to be drawn callback\_context The callback context #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef void(\* MenuLayerDrawSeparatorCallback)(GContext \*ctx, const Layer \*cell\_layer, MenuIndex \*cell\_index, void \*callback\_context) Function signature for the callback to render the separator at a given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). ##### Note The `cell_layer` argument is provided to make it easy to re-use an `.update_proc` implementation in this callback. Only the bounds and frame of the `cell_layer` are actually valid and other properties should be ignored. #### Parameters ctx The destination graphics context to draw into cell\_layer The cell's layer, containing the geometry of the cell cell\_index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the separator that needs to be drawn callback\_context The callback context #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef void(\* MenuLayerSelectCallback)(struct MenuLayer \*menu\_layer, MenuIndex \*cell\_index, void \*callback\_context) Function signature for the callback to handle the event that a user hits the SELECT button. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the selection event occured cell\_index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the cell that is selected callback\_context The callback context #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef void(\* MenuLayerSelectionChangedCallback)(struct MenuLayer \*menu\_layer, MenuIndex new\_index, MenuIndex old\_index, void \*callback\_context) Function signature for the callback to handle a change in the current selected item in the menu. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the selection event occured new\_index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the new item that is selected now old\_index The [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the old item that was selected before callback\_context The callback context #### See Also [menu\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) typedef void(\* MenuLayerSelectionWillChangeCallback)(struct MenuLayer \*menu\_layer, MenuIndex \*new\_index, MenuIndex old\_index, void \*callback\_context) Function signature for the callback which allows or changes selection behavior of the menu. In order to change the cell that should be selected, modify the passed in new\_index. Preventing the selection from changing, new\_index can be assigned the value of old\_index. ##### Note [menu\_layer\_set\_selected\_index](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_selected_index) will not trigger this callback when the selection changes, but [menu\_layer\_set\_selected\_next](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_selected_next) will. #### Parameters menu\_layer The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) for which the selection event that occured new\_index Pointer to the index that the MenuLayer is going to change selection to. old\_index The index that is being unselected. callback\_context The callback context typedef void(\* MenuLayerDrawBackgroundCallback)(GContext \*ctx, const Layer \*bg\_layer, bool highlight, void \*callback\_context) Function signature for the callback which draws the menu's background. The background is underneath the cells of the menu, and is visible in the padding below the bottom cell, or if a cell's background color is set to GColorClear. #### Parameters ctx The destination graphics context to draw into. bg\_layer The background's layer, containing the geometry of the background. highlight Whether this should be rendered as highlighted or not. Highlight style should match the highlight style of cells, since this color can be used for animating selection. ## Macro Definition Documentation #define MENU\_CELL\_BASIC\_HEADER\_HEIGHT ((const int16\_t) 16) Default section header height in pixels. #define MENU\_INDEX\_NOT\_FOUND ((const [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t)) ~0) #define MenuIndex ( section, row) Macro to create a [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). #define MENU\_CELL\_ROUND\_FOCUSED\_SHORT\_CELL\_HEIGHT ((const int16\_t) 68) Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) short cell height when this item is the selected item on a round display. #define MENU\_CELL\_ROUND\_UNFOCUSED\_SHORT\_CELL\_HEIGHT ((const int16\_t) 24) Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) short cell height when this item is not the selected item on a round display. #define MENU\_CELL\_ROUND\_FOCUSED\_TALL\_CELL\_HEIGHT ((const int16\_t) 84) Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) tall cell height when this item is the selected item on a round display. #define MENU\_CELL\_ROUND\_UNFOCUSED\_TALL\_CELL\_HEIGHT ((const int16\_t) 32) Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) tall cell height when this item is not the selected item on a round display. --- # NumberWindow Source: https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow/ A ready-made Window prompting the user to pick a number ![](https://developer.repebble.com/assets/images/docs/c/aplite/number_window.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/number_window.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/number_window.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/number_window.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/number_window.png) ## Function Documentation NumberWindow \* number\_window\_create(const char \* label, [NumberWindowCallbacks](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#NumberWindowCallbacks) callbacks, void \* callback\_context) Creates a new NumberWindow on the heap and initalizes it with the default values. ##### Note The number window is not pushed to the window stack. Use [window\_stack\_push()](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_push) to do this. #### Parameters label The title or prompt to display in the NumberWindow. Must be long-lived and cannot be stack-allocated. callbacks The callbacks callback\_context Pointer to application specific data that is passed #### Returns A pointer to the NumberWindow. `NULL` if the NumberWindow could not be created void number\_window\_destroy(NumberWindow \* number\_window) Destroys a NumberWindow previously created by number\_window\_create. void number\_window\_set\_label(NumberWindow \* numberwindow, const char \* label) Sets the text of the title or prompt label. #### Parameters numberwindow Pointer to the NumberWindow for which to set the label text label The new label text. Must be long-lived and cannot be stack-allocated. void number\_window\_set\_max(NumberWindow \* numberwindow, int32\_t max) Sets the maximum value this field can hold. #### Parameters numberwindow Pointer to the NumberWindow for which to set the maximum value max The maximum value void number\_window\_set\_min(NumberWindow \* numberwindow, int32\_t min) Sets the minimum value this field can hold. #### Parameters numberwindow Pointer to the NumberWindow for which to set the minimum value min The minimum value void number\_window\_set\_value(NumberWindow \* numberwindow, int32\_t value) Sets the current value of the field. #### Parameters numberwindow Pointer to the NumberWindow for which to set the current value value The new current value void number\_window\_set\_step\_size(NumberWindow \* numberwindow, int32\_t step) Sets the amount by which to increment/decrement by on a button click. #### Parameters numberwindow Pointer to the NumberWindow for which to set the step increment step The new step increment int32\_t number\_window\_get\_value(const NumberWindow \* numberwindow) Gets the current value. #### Parameters numberwindow Pointer to the NumberWindow for which to get the current value #### Returns The current value Window \* number\_window\_get\_window(NumberWindow \* numberwindow) Gets the "root" Window of the number window. #### Parameters numberwindow Pointer to the NumberWindow for which to get the "root" Window #### Returns The "root" Window of the number window. ## Data Structure Documentation struct NumberWindowCallbacks Data structure containing all the callbacks for a NumberWindow. #### Data Fields [NumberWindowCallback](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#NumberWindowCallback) incremented Callback that gets called as the value is incremented. Optional, leave `NULL` if unused. [NumberWindowCallback](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#NumberWindowCallback) decremented Callback that gets called as the value is decremented. Optional, leave `NULL` if unused. [NumberWindowCallback](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#NumberWindowCallback) selected Callback that gets called as the value is confirmed, in other words the SELECT button is clicked. Optional, leave `NULL` if unused. ## Typedef Documentation typedef struct NumberWindow NumberWindow typedef void(\* NumberWindowCallback)(struct NumberWindow \*number\_window, void \*context) Function signature for NumberWindow callbacks. --- # PBI File Format Source: https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PBI_File_Format/ PBIs are uncompressed bitmap images with support for color-mapping palettes. PBIs store images either as raw image pixels (1-bit black and white, or 8-bit ARGB) or as palette-based images with 1, 2, or 4 bits per pixel. For palette-based images the pixel data represents the index into the palette, such that each pixel only needs to be large enough to represent the palette size, so - `1-bit` supports up to 2 colors, - `2-bit` supports up to 4 colors, - `4-bit` supports up to 16 colors. The metadata describes how long each row of pixels is in the buffer (the stride). The following restrictions on stride are in place for different formats: - GBitmapFormat1Bit: Each row must be a multiple of 32 pixels (4 bytes). Using the `bounds` field, the area that is actually relevant can be specified. For example, when the image is 29 by 5 pixels (width by height) and the first bit of image data is the pixel at (0, 0), then the bounds.size would be `GSize(29, 5)` and bounds.origin would be `GPoint(0, 0)`. ![](https://developer.repebble.com/assets/images/docs/c/aplite/gbitmap.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/gbitmap.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/gbitmap.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/gbitmap.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/gbitmap.png) - GBitmapFormat8Bit: Each pixel in the bitmap is represented by 1 byte. The color value of that byte correspends to a GColor.argb value. There is no restriction on row\_size\_bytes / stride. - GBitmapFormat1BitPalette, GBitmapFormat2BitPalette, GBitmapFormat4BitPalette: Each pixel in the bitmap is represented by the number of bits the format specifies. Pixels must be packed. For example, in GBitmapFormat2BitPalette, each pixel uses 2 bits. This means 4 pixels / byte. Rows need to be byte-aligned, meaning that there can be up to 3 unused pixels at the end of each line. If the image is 5 pixels wide and 4 pixels tall, row\_size\_bytes = 2, and each row in the bitmap must take 2 bytes, so the bitmap data is 8 bytes in total. Palettized bitmaps also need to have a palette. The palette must be of the correct size, which is specified by the format. For example, GBitmapFormat4BitPalette uses 4 bits per pixel, meaning that there must be 2^4 = 16 colors in the palette. The Basalt Platform provides for 2-bits per color channel, so images are optimized by the SDK tooling when loaded as a resource-type "pbi" to the Pebble's 64-colors with 4 levels of transparency. This optimization also handles mapping unsupported colors to the nearest supported color, and reducing the pixel depth to the number of bits required to support the optimized number of colors. --- # Platform Source: https://developer.repebble.com/docs/c/Foundation/Platform/ ## Enum Documentation enum PlatformType #### Enumerators PlatformTypeAplitePlatformTypeBasaltPlatformTypeChalkPlatformTypeDioritePlatformTypeEmeryPlatformTypeFlintPlatformTypeGabbro ## Macro Definition Documentation #define PBL\_PLATFORM\_TYPE\_CURRENT PlatformTypeEmery #define PBL\_PLATFORM\_SWITCH\_DEFAULT ( PLAT, DEFAULT, APLITE, BASALT, CHALK, DIORITE, EMERY, FLINT, GABBRO) #define PBL\_PLATFORM\_SWITCH ( PLAT, APLITE, BASALT, CHALK, DIORITE, EMERY, FLINT, GABBRO) --- # PNG8 File Format Source: https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PNG8_File_Format/ Pebble supports both a PBIs (uncompressed bitmap images) as well as PNG8 images. PNG images are compressed allowing for storage savings up to 90%. PNG8 is a PNG that uses palette-based or grayscale images with 1, 2, 4 or 8 bits per pixel. For palette-based images the pixel data represents the index into the palette, such that each pixel only needs to be large enough to represent the palette size, so - `1-bit` supports up to 2 colors, - `2-bit` supports up to 4 colors, - `4-bit` supports up to 16 colors, - `8-bit` supports up to 256 colors. There are 2 parts to the palette: the RGB24 color-mapping palette ("PLTE"), and the optional 8-bit transparency palette ("tRNs"). A pixel's color index maps to both tables, combining to allow the pixel to have both color as well as transparency. For grayscale images, the pixel data represents the luminosity (or shade of gray). - `1-bit` supports black and white - `2-bit` supports black, dark\_gray, light\_gray and white - `4-bit` supports black, white and 14 shades of gray - `8-bit` supports black, white and 254 shades of gray Optionally, grayscale images allow for 1 fully transparent color, which is removed from the fully-opaque colors above (e.g. a 2 bit grayscale image can have black, white, dark\_gray and a transparent color). The Basalt Platform provides for 2-bits per color channel, so images are optimized by the SDK tooling when loaded as a resource-type "png" to the Pebble's 64-colors with 4 levels of transparency. This optimization also handles mapping unsupported colors to the nearest supported color, and reducing the pixel depth to the number of bits required to support the optimized number of colors. PNG8 images from other sources are supported, with the colors truncated to match supported colors at runtime. --- # Preferences Source: https://developer.repebble.com/docs/c/User_Interface/Preferences/ Values recommended by the system ## Function Documentation [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) preferred\_result\_display\_duration(void) Get the recommended amount of milliseconds a result window should be visible before it should automatically close. ##### Note It is the application developer's responsibility to automatically close a result window. #### Returns The recommended result window timeout duration in milliseconds [PreferredContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSize) preferred\_content\_size(void) Returns the user's preferred content size representing the scale of all the app's UI components should use for display. #### Returns The user's [PreferredContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSize) setting. bool quiet\_time\_is\_active(void) Users can toggle Quiet Time manually or on schedule. Watchfaces and apps should respect this choice and avoid disturbing actions such as vibration if quiet time is active. #### Returns True, if Quiet Time is currently active. ## Enum Documentation enum PreferredContentSize [PreferredContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSize) represents the display scale of all the app's UI components. The enum contains all sizes that all platforms as a whole are capable of displaying, but each individual platform may not be able to display all sizes. As of version 4.1, platforms other than Emery cannot display extra large and Emery itself cannot display small. #### Enumerators PreferredContentSizeSmallPreferredContentSizeMediumPreferredContentSizeLargePreferredContentSizeExtraLargeNumPreferredContentSizes --- # PropertyAnimation Source: https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation/ A ProperyAnimation animates the value of a "property" of a "subject" over time. ### Animating a Layer's frame property Currently there is only one specific type of property animation offered off-the-shelf, namely one to change the frame (property) of a layer (subject), see [property\_animation\_create\_layer\_frame()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create_layer_frame). ### Implementing a custom PropertyAnimation It is fairly simple to create your own variant of a PropertyAnimation. Please refer to [User Interface Layers chapter in the Pebble Developer Guides](https://developer.repebble.com/guides/user-interfaces/layers.md) (chapter "Property Animations") for a conceptual overview of the animation framework and make sure you understand the underlying [Animation](https://developer.repebble.com/docs/c/User_Interface/Animation.md), in case you are not familiar with it, before trying to implement a variation on PropertyAnimation. To implement a custom property animation, use [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) and provide a function pointers to the accessors (getter and setter) and setup, update and teardown callbacks in the implementation argument. Note that the type of property to animate with [PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md) is limited to int16\_t, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) or [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect). For each of these types, there are implementations provided for the necessary `.update` handler of the animation: see [property\_animation\_update\_int16()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16), [property\_animation\_update\_gpoint()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gpoint) and [property\_animation\_update\_grect()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_grect). These update functions expect the `.accessors` to conform to the following interface: Any getter needs to have the following function signature: ` __type__ getter(void *subject);` Any setter needs to have to following function signature: `void setter(void *subject, __type__ value);` See [Int16Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Getter), [Int16Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Setter), [GPointGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointGetter), [GPointSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointSetter), [GRectGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectGetter), [GRectSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectSetter) for the typedefs that accompany the update fuctions. ``` static const PropertyAnimationImplementation my\_implementation = {.base = {// using the "stock" update callback:.update = (AnimationUpdateImplementation) property\_animation\_update\_gpoint,},.accessors = {// my accessors that get/set a GPoint from/onto my subject:.setter = { .gpoint = my\_layer\_set\_corner\_point, },.getter = { .gpoint = (const GPointGetter) my\_layer\_get\_corner\_point, },},};static PropertyAnimation\* s\_my\_animation\_ptr = NULL;static GPoint s\_to\_point = GPointZero;...// Use NULL as 'from' value, this will make the animation framework call the getter// to get the current value of the property and use that as the 'from' value:s\_my\_animation\_ptr = property\_animation\_create(&my\_implementation, my\_layer, NULL, &s\_to\_point);animation\_schedule(property\_animation\_get\_animation(s\_my\_animation\_ptr)); ``` ## Function Documentation PropertyAnimation \* property\_animation\_create\_layer\_frame(struct Layer \* layer, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* from\_frame, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) \* to\_frame) Convenience function to create and initialize a property animation that animates the frame of a Layer. It sets up the PropertyAnimation to use [layer\_set\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) and [layer\_get\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_frame) as accessors and uses the `layer` parameter as the subject for the animation. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). ##### Note Pass in `NULL` as one of the frame arguments to have it set automatically to the layer's current frame. This will result in a call to [layer\_get\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_frame) to get the current frame of the layer. #### Parameters layer the layer that will be animated from\_frame the frame that the layer should animate from to\_frame the frame that the layer should animate to #### Returns A handle to the property animation. `NULL` if animation could not be created - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) PropertyAnimation \* property\_animation\_create\_bounds\_origin(struct Layer \* layer, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* from, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* to) Convenience function to create and initialize a property animation that animates the bound's origin of a Layer. It sets up the PropertyAnimation to use [layer\_set\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) and [layer\_get\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds) as accessors and uses the `layer` parameter as the subject for the animation. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). #### Parameters layer the layer that will be animated from\_origin the origin that the bounds should animate from to\_origin the origin that the layer should animate to #### Returns A handle to the property animation. `NULL` if animation could not be created PropertyAnimation \* property\_animation\_create\_bounds\_origin(struct Layer \* layer, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* from, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* to) Convenience function to create and initialize a property animation that animates the bound's origin of a Layer. It sets up the PropertyAnimation to use [layer\_set\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) and [layer\_get\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds) as accessors and uses the `layer` parameter as the subject for the animation. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). #### Parameters layer the layer that will be animated from\_origin the origin that the bounds should animate from to\_origin the origin that the layer should animate to #### Returns A handle to the property animation. `NULL` if animation could not be created PropertyAnimation \* property\_animation\_create\_bounds\_origin(struct Layer \* layer, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* from, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) \* to) Convenience function to create and initialize a property animation that animates the bound's origin of a Layer. It sets up the PropertyAnimation to use [layer\_set\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) and [layer\_get\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds) as accessors and uses the `layer` parameter as the subject for the animation. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). #### Parameters layer the layer that will be animated from the origin that the bounds should animate from to the origin that the layer should animate to #### Returns A handle to the property animation. `NULL` if animation could not be created PropertyAnimation \* property\_animation\_create(const [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation) \* implementation, void \* subject, void \* from\_value, void \* to\_value) Creates a new PropertyAnimation on the heap and and initializes it with the specified values. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). If the `from_value` or the `to_value` is `NULL`, the getter accessor will be called to get the current value of the property and be used instead. ##### Note Pass in `NULL` as one of the value arguments to have it set automatically to the subject's current property value, as returned by the getter function. Also note that passing in `NULL` for both `from_value` and `to_value`, will result in the animation having the same from- and to- values, effectively not doing anything. #### Parameters implementation Pointer to the implementation of the animation. In most cases, it makes sense to pass in a `static const` struct pointer. subject Pointer to the "subject" being animated. This will be passed in when the getter/ setter accessors are called, see [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors), [GPointSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointSetter), and friends. The value of this pointer will be copied into the `.subject` field of the PropertyAnimation struct. from\_value Pointer to the value that the subject should animate from to\_value Pointer to the value that the subject should animate to #### Returns A handle to the property animation. `NULL` if animation could not be created void property\_animation\_destroy(PropertyAnimation \* property\_animation) Destroy a property animation allocated by [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) or relatives. #### Parameters property\_animation the return value from property\_animation\_create void property\_animation\_update\_int16(PropertyAnimation \* property\_animation, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) distance\_normalized) Default update callback for a property animations to update a property of type int16\_t. Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [Int16Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Getter) and [Int16Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Setter). The implementation of this function will calculate the next value of the animation and call the setter to set the new value upon the subject. ##### Note This function is not supposed to be called "manually", but will be called automatically when the animation is being run. #### Parameters property\_animation The property animation for which the update is requested. distance\_normalized The current normalized distance. See [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) void property\_animation\_update\_uint32(PropertyAnimation \* property\_animation, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) distance\_normalized) Default update callback for a property animations to update a property of type [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t). Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [UInt32Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#UInt32Getter) and [UInt32Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#UInt32Setter). The implementation of this function will calculate the next value of the animation and call the setter to set the new value upon the subject. ##### Note This function is not supposed to be called "manually", but will be called automatically when the animation is being run. #### Parameters property\_animation The property animation for which the update is requested. distance\_normalized The current normalized distance. See [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) void property\_animation\_update\_gpoint(PropertyAnimation \* property\_animation, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) distance\_normalized) Default update callback for a property animations to update a property of type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint). Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [GPointGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointGetter) and [GPointSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointSetter). The implementation of this function will calculate the next point of the animation and call the setter to set the new point upon the subject. ##### Note This function is not supposed to be called "manually", but will be called automatically when the animation is being run. #### Parameters property\_animation The property animation for which the update is requested. distance\_normalized The current normalized distance. See [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) void property\_animation\_update\_grect(PropertyAnimation \* property\_animation, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) distance\_normalized) Default update callback for a property animations to update a property of type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect). Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [GRectGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectGetter) and [GRectSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectSetter). The implementation of this function will calculate the next rectangle of the animation and call the setter to set the new rectangle upon the subject. ##### Note This function is not supposed to be called "manually", but will be called automatically when the animation is being run. #### Parameters property\_animation The property animation for which the update is requested. distance\_normalized The current normalized distance. See [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) void property\_animation\_update\_gcolor8(PropertyAnimation \* property\_animation, const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) distance\_normalized) Default update callback for a property animations to update a property of type GColor8. Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [GColor8Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GColor8Getter) and [GColor8Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GColor8Setter). The implementation of this function will calculate the next rectangle of the animation and call the setter to set the new value upon the subject. ##### Note This function is not supposed to be called "manually", but will be called automatically when the animation is being run. #### Parameters property\_animation The property animation for which the update is requested. distance\_normalized The current normalized distance. See [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) Animation \* property\_animation\_get\_animation(PropertyAnimation \* property\_animation) Convenience function to retrieve an animation instance from a property animation instance. #### Parameters property\_animation The property animation #### Returns The [Animation](https://developer.repebble.com/docs/c/User_Interface/Animation.md) within this PropertyAnimation bool property\_animation\_subject(PropertyAnimation \* property\_animation, void \*\* subject, bool set) Helper function used by the property\_animation\_get|set\_subject macros. #### Parameters property\_animation Handle to the property animation subject The subject to get or set. set true to set new subject, false to retrieve existing value #### Returns true if successful, false on failure (usually a bad animation\_h) bool property\_animation\_from(PropertyAnimation \* property\_animation, void \* from, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) size, bool set) Helper function used by the property\_animation\_get|set\_from\_.\* macros. #### Parameters property\_animation Handle to the property animation from Pointer to the value size Size of the from value set true to set new value, false to retrieve existing one #### Returns true if successful, false on failure (usually a bad animation\_h) bool property\_animation\_to(PropertyAnimation \* property\_animation, void \* to, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) size, bool set) Helper function used by the property\_animation\_get|set\_to\_.\* macros. #### Parameters property\_animation handle to the property animation to Pointer to the value size Size of the to value set true to set new value, false to retrieve existing one #### Returns true if successful, false on failure (usually a bad animation\_h) ## Data Structure Documentation struct PropertyAnimationAccessors Data structure containing the setter and getter function pointers that the property animation should use. The specified setter function will be used by the animation's update callback. Based on the type of the property (int16\_t, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) or [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect)), the accompanying update callback should be used, see [property\_animation\_update\_int16()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16), [property\_animation\_update\_gpoint()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gpoint) and [property\_animation\_update\_grect()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_grect). The getter function is used when the animation is initialized, to assign the current value of the subject's property as "from" or "to" value, see [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create). #### Data Fields union [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) setter Function pointer to the implementation of the function that **sets** the updated property value. This function will be called repeatedly for each animation frame. union [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) getter Function pointer to the implementation of the function that **gets** the current property value. This function will be called during [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create), to get the current property value, in case the `from_value` or `to_value` argument is `NULL`. struct PropertyAnimationImplementation Data structure containing a collection of function pointers that form the implementation of the property animation. See the code example at the top ([PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md)). #### Data Fields [AnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationImplementation) base The "inherited" fields from the Animation "base class". [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) accessors The accessors to set/get the property to be animated. ## Typedef Documentation typedef struct PropertyAnimation PropertyAnimation typedef [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) GPointReturn Work-around for function pointer return type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) to avoid tripping the pre-processor to use the equally named [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) define. typedef [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) GRectReturn Work-around for function pointer return type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) to avoid tripping the pre-processor to use the equally named [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) define. typedef void(\* Int16Setter)(void \*subject, int16\_t int16) Function signature of a setter function to set a property of type int16\_t onto the subject. #### See Also [property\_animation\_update\_int16()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef int16\_t(\* Int16Getter)(void \*subject) Function signature of a getter function to get the current property of type int16\_t of the subject. #### See Also [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef void(\* UInt32Setter)(void \*subject, uint32\_t uint32) Function signature of a setter function to set a property of type [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) onto the subject. #### See Also [property\_animation\_update\_int16()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t)(\* UInt32Getter)(void \*subject) Function signature of a getter function to get the current property of type [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) of the subject. #### See Also [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef void(\* GPointSetter)(void \*subject, GPoint gpoint) Function signature of a setter function to set a property of type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) onto the subject. #### See Also [property\_animation\_update\_gpoint()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gpoint) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef [GPointReturn](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointReturn)(\* GPointGetter)(void \*subject) Function signature of a getter function to get the current property of type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) of the subject. #### See Also [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef void(\* GRectSetter)(void \*subject, GRect grect) Function signature of a setter function to set a property of type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) onto the subject. #### See Also [property\_animation\_update\_grect()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_grect) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef [GRectReturn](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectReturn)(\* GRectGetter)(void \*subject) Function signature of a getter function to get the current property of type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) of the subject. #### See Also [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef void(\* GColor8Setter)(void \*subject, GColor8 gcolor) Function signature of a setter function to set a property of type GColor8 onto the subject. #### See Also [property\_animation\_update\_gcolor8()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gcolor8) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) typedef GColor8(\* GColor8Getter)(void \*subject) Function signature of a getter function to get the current property of type GColor8 of the subject. #### See Also [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) ## Macro Definition Documentation #define property\_animation\_clone ( property\_animation) Convenience function to clone a property animation instance. #### Parameters property\_animation The property animation #### Returns A clone of the original Animation #define property\_animation\_get\_from\_grect ( property\_animation, value\_ptr) Convenience function to retrieve the 'from' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value from property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr The value will be retrieved into this pointer #### Returns true on success, false on failure #define property\_animation\_set\_from\_grect ( property\_animation, value\_ptr) Convenience function to set the 'from' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value of property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer to the new value #### Returns true on success, false on failure #define property\_animation\_get\_from\_gpoint ( property\_animation, value\_ptr) Convenience function to retrieve the 'from' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value from property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr The value will be retrieved into this pointer #### Returns true on success, false on failure #define property\_animation\_set\_from\_gpoint ( property\_animation, value\_ptr) Convenience function to set the 'from' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value of property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer to the new value #### Returns true on success, false on failure #define property\_animation\_get\_from\_int16 ( property\_animation, value\_ptr) Convenience function to retrieve the 'from' int16\_t value from property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr The value will be retrieved into this pointer #### Returns true on success, false on failure #define property\_animation\_set\_from\_int16 ( property\_animation, value\_ptr) Convenience function to set the 'from' int16\_t value of property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer to the new value #### Returns true on success, false on failure #define property\_animation\_get\_to\_grect ( property\_animation, value\_ptr) Convenience function to retrieve the 'to' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value from property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr The value will be retrieved into this pointer #### Returns true on success, false on failure #define property\_animation\_set\_to\_grect ( property\_animation, value\_ptr) Convenience function to set the 'to' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value of property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer to the new value #### Returns true on success, false on failure #define property\_animation\_get\_to\_gpoint ( property\_animation, value\_ptr) Convenience function to retrieve the 'to' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value from property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr The value will be retrieved into this pointer #### Returns true on success, false on failure #define property\_animation\_set\_to\_gpoint ( property\_animation, value\_ptr) Convenience function to set the 'to' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value of property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer to the new value #### Returns true on success, false on failure #define property\_animation\_get\_to\_int16 ( property\_animation, value\_ptr) Convenience function to retrieve the 'to' int16\_t value from property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr The value will be retrieved into this pointer #### Returns true on success, false on failure #define property\_animation\_set\_to\_int16 ( property\_animation, value\_ptr) Convenience function to set the 'to' int16\_t value of property animation handle. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer to the new value #### Returns true on success, false on failure #define property\_animation\_get\_subject ( property\_animation, value\_ptr) Retrieve the subject of a property animation. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer used to store the subject of this property animation #### Returns The subject of this PropertyAnimation #define property\_animation\_set\_subject ( property\_animation, value\_ptr) Set the subject of a property animation. #### Parameters property\_animation The PropertyAnimation to be accessed value\_ptr Pointer to the new subject value --- # Resources Source: https://developer.repebble.com/docs/c/Foundation/Resources/ Managing application resources Resources are data files that are bundled with your application binary and can be loaded at runtime. You use resources to embed images or custom fonts in your app, but also to embed any data file. Resources are always read-only. Resources are stored on Pebble’s flash memory and only loaded in RAM when you load them. This means that you can have a large number of resources embedded inside your app, even though Pebble’s RAM memory is very limited. See [App Resources in the Pebble Developer Guides](https://developer.repebble.com/guides/pebble-apps/resources/) for information on how to embed resources into your app's bundle. ## Modules #### [File Formats](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats.md)   ## Function Documentation [ResHandle](https://developer.repebble.com/docs/c/Foundation/Resources.md#ResHandle) resource\_get\_handle([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) resource\_id) Gets the resource handle for a file identifier. The resource IDs are auto-generated by the Pebble build process, based on the `appinfo.json`. The "name" field of each resource is prefixed by `RESOURCE_ID_` and made visible to the application (through the `build/src/resource_ids.auto.h` header which is automatically included). For example, given the following fragment of `appinfo.json`: ``` ..."resources" : {"media": [{"name": "MY\_ICON","file": "img/icon.png","type": "png",}, ... ``` The generated file identifier for this resource is `RESOURCE_ID_MY_ICON`. To get a resource handle for that resource write: ``` ResHandle rh = resource\_get\_handle(RESOURCE\_ID\_MY\_ICON); ``` #### Parameters resource\_id The resource ID [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) resource\_size([ResHandle](https://developer.repebble.com/docs/c/Foundation/Resources.md#ResHandle) h) Gets the size of the resource given a resource handle. #### Parameters h The handle to the resource #### Returns The size of the resource in bytes [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) resource\_load([ResHandle](https://developer.repebble.com/docs/c/Foundation/Resources.md#ResHandle) h, uint8\_t \* buffer, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) max\_length) Copies the bytes for the resource with a given handle from flash storage into a given buffer. #### Parameters h The handle to the resource buffer The buffer to load the resource data into max\_length The maximum number of bytes to copy #### Returns The number of bytes actually copied [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) resource\_load\_byte\_range([ResHandle](https://developer.repebble.com/docs/c/Foundation/Resources.md#ResHandle) h, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) start\_offset, uint8\_t \* buffer, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) num\_bytes) Copies a range of bytes from a resource with a given handle into a given buffer. #### Parameters h The handle to the resource start\_offset The offset in bytes at which to start reading from the resource buffer The buffer to load the resource data into num\_bytes The maximum number of bytes to copy #### Returns The number of bytes actually copied ## Typedef Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) typedef void \* ResHandle Opaque reference to a resource. #### See Also [resource\_get\_handle()](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_get_handle) typedef void \* ResHandle Opaque reference to a resource. #### See Also [resource\_get\_handle()](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_get_handle) typedef void \* ResHandle Opaque reference to a resource. #### See Also `resource_get_handle()` ## Macro Definition Documentation #define RESOURCE\_ID\_FONT\_FALLBACK RESOURCE\_ID\_GOTHIC\_14 --- # RotBitmapLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer/ Layer that displays a rotated bitmap image. A RotBitmapLayer is like a [BitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md) but has the ability to be rotated (by default, around its center). The amount of rotation is specified using [rot\_bitmap\_layer\_set\_angle()](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_set_angle) or [rot\_bitmap\_layer\_increment\_angle()](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_increment_angle). The rotation argument to those functions is specified as an amount of clockwise rotation, where the value 0x10000 represents a full 360 degree rotation and 0 represent no rotation, and it scales linearly between those values, just like [sin\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#sin_lookup). The center of rotation in the source bitmap is always placed at the center of the RotBitmapLayer and the size of the RotBitmapLayer is automatically calculated so that the entire Bitmap can fit in at all rotation angles. For example, if the image is 10px wide and high, the RotBitmapLayer will be 14px wide ( sqrt(10^2+10^2) ). By default, the center of rotation in the source bitmap is the center of the bitmap but you can call [rot\_bitmap\_set\_src\_ic()](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_set_src_ic) to change the center of rotation. ## Function Documentation RotBitmapLayer \* rot\_bitmap\_layer\_create(GBitmap \* bitmap) Creates a new RotBitmapLayer on the heap and initializes it with the default values: - Angle: 0 - Compositing mode: GCompOpAssign - Corner clip color: GColorClear #### Parameters bitmap The bitmap to display in this RotBitmapLayer #### Returns A pointer to the RotBitmapLayer. `NULL` if the RotBitmapLayer could not be created void rot\_bitmap\_layer\_destroy(RotBitmapLayer \* bitmap) Destroys a RotBitmapLayer and frees all associated memory. ##### Note It is the developer responsibility to free the GBitmap. #### Parameters bitmap The RotBitmapLayer to destroy. void rot\_bitmap\_layer\_set\_corner\_clip\_color(RotBitmapLayer \* bitmap, GColor color) Defines what color to use in areas that are not covered by the source bitmap. By default this is GColorClear. #### Parameters bitmap The RotBitmapLayer on which to change the corner clip color color The corner clip color void rot\_bitmap\_layer\_set\_angle(RotBitmapLayer \* bitmap, int32\_t angle) Sets the rotation angle of this RotBitmapLayer. #### Parameters bitmap The RotBitmapLayer on which to change the rotation angle Rotation is an integer between 0 (no rotation) and 0x10000 (360 degree rotation). #### See Also [sin\_lookup()](https://developer.repebble.com/docs/c/Foundation/Math.md#sin_lookup) void rot\_bitmap\_layer\_increment\_angle(RotBitmapLayer \* bitmap, int32\_t angle\_change) Change the rotation angle of this RotBitmapLayer. #### Parameters bitmap The RotBitmapLayer on which to change the rotation angle\_change The rotation angle change void rot\_bitmap\_set\_src\_ic(RotBitmapLayer \* bitmap, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) ic) Defines the only point that will not be affected by the rotation in the source bitmap. For example, if you pass [GPoint(0, 0)](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint), the image will rotate around the top-left corner. This point is always projected at the center of the RotBitmapLayer. Calling this function automatically adjusts the width and height of the RotBitmapLayer so that the entire bitmap can fit inside the layer at all rotation angles. #### Parameters bitmap The RotBitmapLayer on which to change the rotation ic The only point in the original image that will not be affected by the rotation. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void rot\_bitmap\_set\_compositing\_mode(RotBitmapLayer \* bitmap, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the compositing mode of how the bitmap image is composited onto what has been drawn beneath the RotBitmapLayer. By default this is GCompOpAssign. The RotBitmapLayer is automatically marked dirty after this operation. #### Parameters bitmap The RotBitmapLayer on which to change the rotation mode The compositing mode to set #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) void rot\_bitmap\_set\_compositing\_mode(RotBitmapLayer \* bitmap, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the compositing mode of how the bitmap image is composited onto what has been drawn beneath the RotBitmapLayer. By default this is GCompOpAssign. The RotBitmapLayer is automatically marked dirty after this operation. #### Parameters bitmap The RotBitmapLayer on which to change the rotation mode The compositing mode to set #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) void rot\_bitmap\_set\_compositing\_mode(RotBitmapLayer \* bitmap, [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) mode) Sets the compositing mode of how the bitmap image is composited onto what has been drawn beneath the RotBitmapLayer. By default this is GCompOpAssign, i.e. transparency disabled. The RotBitmapLayer is automatically marked dirty after this operation. #### Parameters bitmap The RotBitmapLayer on which to change the rotation mode The compositing mode to set #### See Also [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) Layer \* rot\_bitmap\_layer\_get\_layer(const RotBitmapLayer \* rot\_bitmap\_layer) Gets the "root" Layer of the RotBitmapLayer, which is the parent for the sub- layers used for its implementation. #### Parameters rot\_bitmap\_layer Pointer to the RotBitmapLayer for which to get the "root" Layer #### Returns The "root" Layer of the RotBitmapLayer. ## Typedef Documentation typedef struct RotBitmapLayer RotBitmapLayer --- # ScrollLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer/ Layer that scrolls its contents, animated. ![](https://developer.repebble.com/assets/images/docs/c/aplite/scroll_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/scroll_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/scroll_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/scroll_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/scroll_layer.png) ### Key Points - Facilitates vertical scrolling of a layer sub-hierarchy zero or more arbitrary layers. The example image shows a scroll layer containing one large TextLayer. - Shadows to indicate that there is more content are automatically drawn on top of the content. When the end of the scroll layer is reached, the shadow will automatically be retracted. - Scrolling from one offset to another is animated implicitly by default. - The scroll layer contains a "content" sub-layer, which is the layer that is actually moved up an down. Any layer that is a child of this "content" sub-layer, will be moved as well. Effectively, an entire layout of layers can be scrolled this way. Use the convenience function [scroll\_layer\_add\_child()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_add_child) to add child layers to the "content" sub-layer. - The scroll layer needs to be informed of the total size of the contents, in order to calculate from and to what point it should be able to scroll. Use [scroll\_layer\_set\_content\_size()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_size) to set the size of the contents. - The button behavior is set up, using the convenience function [scroll\_layer\_set\_click\_config\_onto\_window()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window). This will associate the UP and DOWN buttons with scrolling up and down. - The SELECT button can be configured by installing a click configuration provider using [scroll\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_callbacks). - To scroll programatically to a certain offset, use [scroll\_layer\_set\_content\_offset()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_offset). - It is possible to get called back for each scrolling increment, by installing the `.content_offset_changed_handler` callback using [scroll\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_callbacks). - Only vertical scrolling is supported at the moment. ## Function Documentation ScrollLayer \* scroll\_layer\_create([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame) Creates a new ScrollLayer on the heap and initalizes it with the default values: - Clips: `true` - Hidden: `false` - Content size: `frame.size` - Content offset: [GPointZero](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPointZero) - Callbacks: None (`NULL` for each one) - Callback context: `NULL` #### Returns A pointer to the ScrollLayer. `NULL` if the ScrollLayer could not be created void scroll\_layer\_destroy(ScrollLayer \* scroll\_layer) Destroys a ScrollLayer previously created by scroll\_layer\_create. Layer \* scroll\_layer\_get\_layer(const ScrollLayer \* scroll\_layer) Gets the "root" Layer of the scroll layer, which is the parent for the sub- layers used for its implementation. #### Parameters scroll\_layer Pointer to the ScrollLayer for which to get the "root" Layer #### Returns The "root" Layer of the scroll layer. void scroll\_layer\_add\_child(ScrollLayer \* scroll\_layer, Layer \* child) Adds the child layer to the content sub-layer of the ScrollLayer. This will make the child layer part of the scrollable contents. The content sub-layer of the ScrollLayer will become the parent of the child layer. ##### Note You may need to update the size of the scrollable contents using [scroll\_layer\_set\_content\_size()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_size). #### Parameters scroll\_layer The ScrollLayer to which to add the child layer. child The Layer to add to the content sub-layer of the ScrollLayer. void scroll\_layer\_set\_click\_config\_onto\_window(ScrollLayer \* scroll\_layer, struct Window \* window) Convenience function to set the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) callback on the given window to scroll layer's internal click config provider. This internal click configuration provider, will set up the default UP & DOWN scrolling behavior. This function calls [window\_set\_click\_config\_provider\_with\_context](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) to accomplish this. If you application has set a `.click_config_provider` callback using [scroll\_layer\_set\_callbacks()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_callbacks), this will be called by the internal click config provider, after configuring the UP & DOWN buttons. This allows your application to configure the SELECT button behavior and optionally override the UP & DOWN button behavior. The callback context for the SELECT click recognizer is automatically set to the scroll layer's context (see [scroll\_layer\_set\_context()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_context) ). This context is passed into [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) callbacks. For the UP and DOWN buttons, the scroll layer itself is passed in by default as the callback context in order to deal with those buttons presses to scroll up and down automatically. #### Parameters scroll\_layer The ScrollLayer that needs to receive click events. window The window for which to set the click configuration. #### See Also [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) [window\_set\_click\_config\_provider\_with\_context](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) void scroll\_layer\_set\_callbacks(ScrollLayer \* scroll\_layer, [ScrollLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ScrollLayerCallbacks) callbacks) Sets the callbacks that the scroll layer exposes. The `context` as set by [scroll\_layer\_set\_context()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_context) is passed into each of the callbacks. See [ScrollLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ScrollLayerCallbacks) for the different callbacks. ##### Note If the `context` is NULL, a pointer to scroll\_layer is used as context parameter instead when calling callbacks. #### Parameters scroll\_layer The ScrollLayer for which to assign new callbacks. callbacks The new callbacks. void scroll\_layer\_set\_context(ScrollLayer \* scroll\_layer, void \* context) Sets a new callback context. This context is passed into the scroll layer's callbacks and also the [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) for the SELECT button. If `NULL` or not set, the context defaults to a pointer to the ScrollLayer itself. #### Parameters scroll\_layer The ScrollLayer for which to assign the new callback context. context The new callback context. #### See Also [scroll\_layer\_set\_click\_config\_onto\_window](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window) [scroll\_layer\_set\_callbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_callbacks) void scroll\_layer\_set\_content\_offset(ScrollLayer \* scroll\_layer, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) offset, bool animated) Scrolls to the given offset, optionally animated. ##### Notes When scrolling down, the offset's `.y` decrements. When scrolling up, the offset's `.y` increments. If scrolled completely to the top, the offset is [GPointZero](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPointZero). The `.x` field must be `0`. Horizontal scrolling is not supported. #### Parameters scroll\_layer The ScrollLayer for which to set the content offset offset The final content offset animated Pass in `true` to animate to the new content offset, or `false` to set the new content offset without animating. #### See Also [scroll\_layer\_get\_content\_offset](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_content_offset) [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) scroll\_layer\_get\_content\_offset(ScrollLayer \* scroll\_layer) Gets the point by which the contents are offset. #### Parameters scroll\_layer The ScrollLayer for which to get the content offset #### See Also [scroll\_layer\_set\_content\_offset](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_offset) void scroll\_layer\_set\_content\_size(ScrollLayer \* scroll\_layer, [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) size) Sets the size of the contents layer. This determines the area that is scrollable. At the moment, this needs to be set "manually" and is not derived from the geometry of the contents layers. #### Parameters scroll\_layer The ScrollLayer for which to set the content size. size The new content size. #### See Also [scroll\_layer\_get\_content\_size](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_content_size) [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) scroll\_layer\_get\_content\_size(const ScrollLayer \* scroll\_layer) Gets the size of the contents layer. #### Parameters scroll\_layer The ScrollLayer for which to get the content size #### See Also [scroll\_layer\_set\_content\_size](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_size) void scroll\_layer\_set\_frame(ScrollLayer \* scroll\_layer, [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame) Set the frame of the scroll layer and adjusts the internal layers' geometry accordingly. The scroll layer is marked dirty automatically. #### Parameters scroll\_layer The ScrollLayer for which to set the frame frame The new frame void scroll\_layer\_scroll\_up\_click\_handler([ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) recognizer, void \* context) The click handlers for the UP button that the scroll layer will install as part of [scroll\_layer\_set\_click\_config\_onto\_window()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window). ##### Note This handler is exposed, in case one wants to implement an alternative handler for the UP button, as a way to invoke the default behavior. #### Parameters recognizer The click recognizer for which the handler is called context A void pointer to the ScrollLayer that is the context of the click event void scroll\_layer\_scroll\_down\_click\_handler([ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) recognizer, void \* context) The click handlers for the DOWN button that the scroll layer will install as part of [scroll\_layer\_set\_click\_config\_onto\_window()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window). ##### Note This handler is exposed, in case one wants to implement an alternative handler for the DOWN button, as a way to invoke the default behavior. #### Parameters recognizer The click recognizer for which the handler is called context A void pointer to the ScrollLayer that is the context of the click event void scroll\_layer\_set\_shadow\_hidden(ScrollLayer \* scroll\_layer, bool hidden) Sets the visibility of the scroll layer shadow. If the visibility has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically on the scroll layer. #### Parameters scroll\_layer The scroll layer for which to set the shadow visibility hidden Supply `true` to make the shadow hidden, or `false` to make it non-hidden. bool scroll\_layer\_get\_shadow\_hidden(const ScrollLayer \* scroll\_layer) Gets the visibility of the scroll layer shadow. #### Parameters scroll\_layer The scroll layer for which to get the visibility #### Returns True if the shadow is hidden, false if it is not hidden. void scroll\_layer\_set\_paging(ScrollLayer \* scroll\_layer, bool paging\_enabled) Enables or disables paging of the ScrollLayer (default: disabled). When enabled, every button press will change the scroll offset by the frame's height. #### Parameters scroll\_layer The scroll layer for which to enable or disable paging paging\_enabled True, if paging should be enabled. False to enable. bool scroll\_layer\_get\_paging(ScrollLayer \* scroll\_layer) Check whether or not the ScrollLayer uses paging when pressing buttons. #### Parameters scroll\_layer The scroll layer for which to get the paging behavior. #### Returns True, if paging is enabled; false otherwise. ContentIndicator \* scroll\_layer\_get\_content\_indicator(ScrollLayer \* scroll\_layer) Gets the ContentIndicator for a ScrollLayer. #### Parameters scroll\_layer The ScrollLayer for which to get the ContentIndicator #### Returns A pointer to the ContentIndicator, or `NULL` upon failure. ContentIndicator \* content\_indicator\_create(void) Creates a ContentIndicator on the heap. #### Returns A pointer to the ContentIndicator. `NULL` if the ContentIndicator could not be created. void content\_indicator\_destroy(ContentIndicator \* content\_indicator) Destroys a ContentIndicator previously created using [content\_indicator\_create()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_create). #### Parameters content\_indicator The ContentIndicator to destroy. bool content\_indicator\_configure\_direction(ContentIndicator \* content\_indicator, [ContentIndicatorDirection](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorDirection) direction, const [ContentIndicatorConfig](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorConfig) \* config) Configures a ContentIndicator for the given direction. #### Parameters content\_indicator The ContentIndicator to configure. direction The direction for which to configure the ContentIndicator. config The configuration to use to configure the ContentIndicator. If NULL, the data for the specified direction will be reset. #### Returns True if the ContentIndicator was successfully configured for the given direction, false otherwise. bool content\_indicator\_get\_content\_available(ContentIndicator \* content\_indicator, [ContentIndicatorDirection](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorDirection) direction) Retrieves the availability status of content in the given direction. #### Parameters content\_indicator The ContentIndicator for which to get the content availability. direction The direction for which to get the content availability. #### Returns True if content is available in the given direction, false otherwise. void content\_indicator\_set\_content\_available(ContentIndicator \* content\_indicator, [ContentIndicatorDirection](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorDirection) direction, bool available) Sets the availability status of content in the given direction. ##### Note If times\_out is enabled, calling this function resets any previously scheduled timeout timer for the ContentIndicator. #### Parameters content\_indicator The ContentIndicator for which to set the content availability. direction The direction for which to set the content availability. available Whether or not content is available. ## Data Structure Documentation struct ContentIndicatorConfig Struct used to configure directions for ContentIndicator. #### Data Fields Layer \* layer The layer where the arrow indicator will be rendered when content is available. bool times\_out Whether the display of the arrow indicator should timeout. [GAlign](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlign) alignment The alignment of the arrow within the provided layer. struct [ContentIndicatorConfig](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorConfig) colors #### See Also [content\_indicator\_configure\_direction](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_configure_direction) struct ScrollLayerCallbacks All the callbacks that the ScrollLayer exposes for use by applications. ##### Note The context parameter can be set using [scroll\_layer\_set\_context()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_context) and gets passed in as context with all of these callbacks. #### Data Fields [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) click\_config\_provider Provider function to set up the SELECT button handlers. This will be called after the scroll layer has configured the click configurations for the up/down buttons, so it can also be used to modify the default up/down scrolling behavior. [ScrollLayerCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ScrollLayerCallback) content\_offset\_changed\_handler Called every time the the content offset changes. During a scrolling animation, it will be called for each intermediary offset as well. ## Enum Documentation enum ContentIndicatorDirection Value to describe directions for ContentIndicator. content\_indicator\_configure\_direction content\_indicator\_set\_content\_available #### Enumerators ContentIndicatorDirectionUp The up direction. ContentIndicatorDirectionDown The down direction. NumContentIndicatorDirections The number of supported directions. ## Typedef Documentation typedef struct ScrollLayer ScrollLayer typedef void(\* ScrollLayerCallback)(struct ScrollLayer \*scroll\_layer, void \*context) Function signature for the `.content_offset_changed_handler` callback. typedef struct ContentIndicator ContentIndicator --- # SimpleMenuLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer/ Wrapper around [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), that uses static data to display a list menu. ![](https://developer.repebble.com/assets/images/docs/c/aplite/simple_menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/simple_menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/simple_menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/simple_menu_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/simple_menu_layer.png) ## Function Documentation SimpleMenuLayer \* simple\_menu\_layer\_create([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame, Window \* window, const [SimpleMenuSection](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#SimpleMenuSection) \* sections, int32\_t num\_sections, void \* callback\_context) Creates a new SimpleMenuLayer on the heap and initializes it. It also sets the internal click configuration provider onto given window. ##### Notes The `sections` array is not deep-copied and can therefore not be stack allocated, but needs to be backed by long-lived storage. This function does not add the menu's layer to the window. #### Parameters frame The frame at which to initialize the menu window The window onto which to set the click configuration provider sections Array with sections that need to be displayed in the menu num\_sections The number of sections in the `sections` array. callback\_context Pointer to application specific data, that is passed into the callbacks. #### Returns A pointer to the SimpleMenuLayer. `NULL` if the SimpleMenuLayer could not be created void simple\_menu\_layer\_destroy(SimpleMenuLayer \* menu\_layer) Destroys a SimpleMenuLayer previously created by simple\_menu\_layer\_create. Layer \* simple\_menu\_layer\_get\_layer(const SimpleMenuLayer \* simple\_menu) Gets the "root" Layer of the simple menu layer, which is the parent for the sub-layers used for its implementation. #### Parameters simple\_menu Pointer to the SimpleMenuLayer for which to get the "root" Layer #### Returns The "root" Layer of the menu layer. int simple\_menu\_layer\_get\_selected\_index(const SimpleMenuLayer \* simple\_menu) Gets the row index of the currently selection menu item. #### Parameters simple\_menu The SimpleMenuLayer for which to get the current selected row index. void simple\_menu\_layer\_set\_selected\_index(SimpleMenuLayer \* simple\_menu, int32\_t index, bool animated) Selects the item in the first section at given row index. #### Parameters simple\_menu The SimpleMenuLayer for which to change the selection index The row index of the item to select animated Supply `true` to animate changing the selection, or `false` to change the selection instantly. MenuLayer \* simple\_menu\_layer\_get\_menu\_layer(SimpleMenuLayer \* simple\_menu) #### Parameters simple\_menu The [SimpleMenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md) to get the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) from. #### Returns The [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). ## Data Structure Documentation struct SimpleMenuItem Data structure containing the information of a menu item. #### Data Fields const char \* title The title of the menu item. Required. const char \* subtitle The subtitle of the menu item. Optional, leave `NULL` if unused. GBitmap \* icon The icon of the menu item. Optional, leave `NULL` if unused. [SimpleMenuLayerSelectCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#SimpleMenuLayerSelectCallback) callback The callback that needs to be called upon a click on the SELECT button. Optional, leave `NULL` if unused. struct SimpleMenuSection Data structure containing the information of a menu section. #### Data Fields const char \* title Title of the section. Optional, leave `NULL` if unused. const [SimpleMenuItem](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#SimpleMenuItem) \* items Array of items in the section. [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_items Number of items in the `.items` array. ## Typedef Documentation typedef struct SimpleMenuLayer SimpleMenuLayer typedef void(\* SimpleMenuLayerSelectCallback)(int index, void \*context) Function signature for the callback to handle the event that a user hits the SELECT button. #### Parameters index The row index of the item context The callback context --- # Speaker Source: https://developer.repebble.com/docs/c/User_Interface/Speaker/ The APIs on this page will only work with SDK 4.9+. Controlling the speaker The Speaker API provides calls that let you play sounds through the watch's speaker. You can play simple note sequences (melodies), single tones, or stream raw PCM audio. Note sequences are compact representations of melodies using MIDI-like note definitions, supporting 4 basic waveforms: sine, square, triangle, and sawtooth. Raw PCM streaming allows apps to generate arbitrary audio in configurable formats. ## Function Documentation bool speaker\_play\_notes(const [SpeakerNote](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerNote) \* notes, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_notes, uint8\_t volume) Play a sequence of notes on the speaker. #### Parameters notes Array of [SpeakerNote](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerNote) structs defining the melody num\_notes Number of notes in the array (max SPEAKER\_MAX\_NOTES) volume Playback volume (0-100) #### Returns true if playback started successfully bool speaker\_play\_tracks(const [SpeakerTrack](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerTrack) \* tracks, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_tracks, uint8\_t volume) Play N monophonic tracks in parallel, mixed (polyphony). #### Parameters tracks Array of track descriptors (notes + optional sample). num\_tracks Number of tracks. Must be \>= 1 and \<= SPEAKER\_MAX\_TRACKS. volume Playback volume (0-100). #### Returns true if playback started successfully. bool speaker\_play\_tone([uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) frequency\_hz, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) duration\_ms, uint8\_t volume, SpeakerWaveform waveform) Play a single tone on the speaker (convenience wrapper). #### Parameters frequency\_hz Tone frequency in Hz duration\_ms Tone duration in milliseconds (max 10000) volume Playback volume (0-100) waveform Waveform to use #### Returns true if playback started successfully bool speaker\_stream\_open([SpeakerPcmFormat](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat) format, uint8\_t volume) Open a raw PCM stream for app-generated audio. #### Parameters format PCM format specifying sample rate and bit depth volume Playback volume (0-100) #### Returns true if stream opened successfully [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) speaker\_stream\_write(const void \* data, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_bytes) Write PCM data to the open stream. #### Parameters data Buffer of PCM data in the format specified at open num\_bytes Number of bytes to write #### Returns Number of bytes actually written (may be less if buffer is full) void speaker\_stream\_close(void) Close the PCM stream. Buffered data will be played before stopping. void speaker\_stop(void) Stop any active speaker playback immediately. void speaker\_set\_volume(uint8\_t volume) Set the speaker volume. #### Parameters volume Volume level (0-100) [SpeakerStatus](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatus) speaker\_get\_status(void) Get the current speaker status. #### Returns Current [SpeakerStatus](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatus) void speaker\_set\_finish\_callback([SpeakerFinishedCallback](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishedCallback) cb, void \* ctx) Register a callback invoked when speaker playback ends. The callback runs on the app task. #### Parameters cb Callback to invoke, or NULL to unregister. ctx User context passed back to cb. bool speaker\_is\_muted(void) Check whether the speaker is currently muted system-wide. The watch can mute the speaker either always-on (Settings → Sounds & Haptics) or for the duration of Quiet Time (Settings → Quiet Time). Apps cannot override the mute, but they can use this to adapt UI or skip long sounds the user won't hear. #### Returns true if the speaker is currently muted ## Data Structure Documentation struct SpeakerNote A single note in a sequence. midi\_note: MIDI note number (0-127, 60=C4). 0 = rest (silence). waveform: SpeakerWaveform value. duration\_ms: Note duration in ms (max 10000). velocity: Volume 0-127 (0 = use global volume). #### Data Fields uint8\_t midi\_noteuint8\_t waveform[uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) duration\_msuint8\_t velocityuint8\_t reserved struct SpeakerSample A raw PCM sample that can be pitch-shifted when played by a track. data: mono signed PCM in the given format. num\_bytes: size of data in bytes. format: sample rate + bit depth (see [SpeakerPcmFormat](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat)). base\_midi\_note: the MIDI note at which the sample plays unshifted (e.g. 60 = C4). Notes above/below this value are produced by resampling. loop: if true, the sample restarts from the beginning each time it runs out, and keeps playing until the owning note's duration elapses. #### Data Fields const void \* data[uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_bytes[SpeakerPcmFormat](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat) formatuint8\_t base\_midi\_notebool loop struct SpeakerTrack A single monophonic voice. Multiple tracks are mixed together by [speaker\_play\_tracks()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tracks) to produce polyphony. notes: array of notes to play sequentially. num\_notes: length of the notes array. sample: if non-NULL, notes are played by pitch-shifting this sample; note.waveform is ignored. If NULL, notes use their waveform field. #### Data Fields const [SpeakerNote](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerNote) \* notes[uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_notesconst [SpeakerSample](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerSample) \* sample ## Enum Documentation enum SpeakerWaveform #### Enumerators SpeakerWaveformSineSpeakerWaveformSquareSpeakerWaveformTriangleSpeakerWaveformSawtoothSpeakerWaveformCount enum SpeakerPcmFormat PCM audio format for speaker streaming. Bit layout: bit0 = sample rate (0=8kHz, 1=16kHz), bit1 = bit depth (0=8-bit, 1=16-bit). All formats are mono signed PCM (8-bit samples are signed [-128,127], not unsigned). #### Enumerators SpeakerPcmFormat\_8kHz\_8bit 8kHz 8-bit signed (1 byte/sample) SpeakerPcmFormat\_16kHz\_8bit 16kHz 8-bit signed (1 byte/sample) SpeakerPcmFormat\_8kHz\_16bit 8kHz 16-bit signed little-endian (2 bytes/sample) SpeakerPcmFormat\_16kHz\_16bit 16kHz 16-bit signed little-endian (2 bytes/sample) SpeakerPcmFormatCount enum SpeakerStatus Speaker status. #### Enumerators SpeakerStatusIdleSpeakerStatusPlayingSpeakerStatusDraining enum SpeakerFinishReason Reason reported when speaker playback ends. #### Enumerators SpeakerFinishReasonDone Playback completed naturally. SpeakerFinishReasonStopped Playback was stopped by the app. SpeakerFinishReasonPreempted Preempted by higher priority source. SpeakerFinishReasonError An error occurred. ## Typedef Documentation typedef void(\* SpeakerFinishedCallback)(SpeakerFinishReason reason, void \*ctx) Callback invoked when playback finishes. #### Parameters reason Why playback ended ctx User context ## Macro Definition Documentation #define SPEAKER\_MAX\_NOTES 256 Maximum number of notes in a single [speaker\_play\_notes()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_notes) call. #define SPEAKER\_MAX\_TRACKS 4 Maximum number of parallel tracks in a single [speaker\_play\_tracks()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tracks) call. #define SPEAKER\_MAX\_SAMPLE\_BYTES\_TOTAL (16 \* 1024) Maximum total sample-data bytes across all tracks in one [speaker\_play\_tracks()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tracks) call. --- # Standard C Source: https://developer.repebble.com/docs/c/Standard_C/ Standard C types, functions, constants, etc. The standard C functions available here, such as [snprintf()](https://developer.repebble.com/docs/c/Standard_C/Format.md#snprintf) and [time()](https://developer.repebble.com/docs/c/Standard_C/Time.md#time), are provided by the firmware. Using these functions will not significantly increase the size of your app beyond what is needed to call the function. You may use other standard C functions not listed here, but be aware that not all will successfully be added to your app, and if they are added, your app's binary size will increase accordingly. ## Modules #### [Format](https://developer.repebble.com/docs/c/Standard_C/Format.md) Standard formatting. #### [Locale](https://developer.repebble.com/docs/c/Standard_C/Locale.md) Standard locale functions. #### [Math](https://developer.repebble.com/docs/c/Standard_C/Math.md) Standard math functions. #### [Memory](https://developer.repebble.com/docs/c/Standard_C/Memory.md) Standard memory functions. #### [String](https://developer.repebble.com/docs/c/Standard_C/String.md) Standard C-string manipulation. #### [Time](https://developer.repebble.com/docs/c/Standard_C/Time.md)   ## Typedef Documentation typedef int uint16\_t 16-bit unsigned integer number typedef int uint32\_t 32-bit unsigned integer number --- # StatusBarLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer/ Layer that serves as a configurable status bar. ## Function Documentation StatusBarLayer \* status\_bar\_layer\_create(void) Creates a new StatusBarLayer on the heap and initializes it with the default values. - Text color: [GColorBlack](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlack) - Background color: [GColorWhite](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorWhite) - Frame: `GRect(0, 0, screen_width, STATUS_BAR_LAYER_HEIGHT)` The status bar is automatically marked dirty after this operation. You can call [layer\_set\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) to create a StatusBarLayer of a different width. ``` // Change the status bar width to make space for the action barint16\_t width = layer\_get\_bounds(root\_layer).size.w - ACTION\_BAR\_WIDTH;GRect frame = GRect(0, 0, width, STATUS\_BAR\_LAYER\_HEIGHT);layer\_set\_frame(status\_bar\_layer\_get\_layer(status\_bar), frame);layer\_add\_child(root\_layer, status\_bar\_layer\_get\_layer(status\_bar)); ``` #### Returns A pointer to the StatusBarLayer, which will be allocated to the heap, `NULL` if the StatusBarLayer could not be created void status\_bar\_layer\_destroy(StatusBarLayer \* status\_bar\_layer) Destroys a StatusBarLayer previously created by status\_bar\_layer\_create. #### Parameters status\_bar\_layer The StatusBarLayer to destroy Layer \* status\_bar\_layer\_get\_layer(StatusBarLayer \* status\_bar\_layer) Gets the "root" Layer of the status bar, which is the parent for the sub- layers used for its implementation. ##### Note The result is always equal to `(Layer *) status_bar_layer`. #### Parameters status\_bar\_layer Pointer to the StatusBarLayer for which to get the "root" Layer #### Returns The "root" Layer of the status bar. GColor status\_bar\_layer\_get\_background\_color(const StatusBarLayer \* status\_bar\_layer) Gets background color of StatusBarLayer. #### Parameters status\_bar\_layer The StatusBarLayer of which to get the color #### Returns GColor of background color property GColor status\_bar\_layer\_get\_foreground\_color(const StatusBarLayer \* status\_bar\_layer) Gets foreground color of StatusBarLayer. #### Parameters status\_bar\_layer The StatusBarLayer of which to get the color #### Returns GColor of foreground color property void status\_bar\_layer\_set\_colors(StatusBarLayer \* status\_bar\_layer, GColor background, GColor foreground) Sets the background and foreground colors of StatusBarLayer. #### Parameters status\_bar\_layer The StatusBarLayer of which to set the colors background The new GColor to set for background foreground The new GColor to set for text and other foreground elements void status\_bar\_layer\_set\_separator\_mode(StatusBarLayer \* status\_bar\_layer, [StatusBarLayerSeparatorMode](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#StatusBarLayerSeparatorMode) mode) Sets the mode of the StatusBarLayer separator, to help divide it from content. #### Parameters status\_bar\_layer The StatusBarLayer of which to set the separator mode mode Determines the separator mode ## Enum Documentation enum StatusBarLayerSeparatorMode Values that are used to indicate the different status bar separator modes. #### Enumerators StatusBarLayerSeparatorModeNone The default mode. No separator will be shown. StatusBarLayerSeparatorModeDotted A dotted separator at the bottom of the status bar. ## Typedef Documentation typedef struct StatusBarLayer StatusBarLayer ## Macro Definition Documentation #define STATUS\_BAR\_LAYER\_HEIGHT [\_STATUS\_BAR\_LAYER\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#_STATUS_BAR_LAYER_HEIGHT)(PBL\_PLATFORM\_TYPE\_CURRENT) The fixed height of the status bar, including separator height. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) #define \_STATUS\_BAR\_LAYER\_HEIGHT ( plat) The fixed height of the status bar, including separator height, for all platforms. #define \_STATUS\_BAR\_LAYER\_HEIGHT ( plat) The fixed height of the status bar, including separator height, for all platforms. #define \_STATUS\_BAR\_LAYER\_HEIGHT ( plat) The fixed height of the status bar, including separator height, for all platforms. --- # Storage Source: https://developer.repebble.com/docs/c/Foundation/Storage/ A mechanism to store persistent application data and state The Persistent Storage API provides you with a mechanism for performing a variety of tasks, like saving user settings, caching data from the phone app, or counting high scores for Pebble watchapp games. In Pebble OS, storage is defined by a collection of fields that you can create, modify or delete. In the API, a field is specified as a key with a corresponding value. Using the Storage API, every app is able to get its own persistent storage space. Each value in that space is associated with a [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key. Storage supports saving integers, strings and byte arrays. The maximum size of byte arrays and strings is defined by PERSIST\_DATA\_MAX\_LENGTH (currently set to 256 bytes). You call the function persist\_exists(key), which returns a boolean indicating if the key exists or not. The Storage API enables your app to save its state, and when compared to using [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md) to retrieve values from the phone, it provides you with a much faster way to restore state. In addition, it draws less power from the battery. The total size of an app's persisted values is capped; call [persist\_get\_max\_size](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_get_max_size) to query the limit at runtime. ## Function Documentation bool persist\_exists(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Checks whether a value has been set for a given key in persistent storage. #### Parameters key The key of the field to check. #### Returns true if a value exists, otherwise false. int persist\_get\_size(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Gets the size of a value for a given key in persistent storage. #### Parameters key The key of the field to lookup the data size. #### Returns The size of the value in bytes or E\_DOES\_NOT\_EXIST if there is no field matching the given key. bool persist\_read\_bool(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Reads a bool value for a given key from persistent storage. If the value has not yet been set, this will return false. #### Parameters key The key of the field to read from. #### Returns The bool value of the key to read from. int32\_t persist\_read\_int(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Reads an int value for a given key from persistent storage. ##### Note The int is a signed 32-bit integer. If the value has not yet been set, this will return 0. #### Parameters key The key of the field to read from. #### Returns The int value of the key to read from. int persist\_read\_data(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, void \* buffer, const [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) buffer\_size) Reads a blob of data for a given key from persistent storage into a given buffer. If the value has not yet been set, the given buffer is left unchanged. #### Parameters key The key of the field to read from. buffer The pointer to a buffer to be written to. buffer\_size The maximum size of the given buffer. #### Returns The number of bytes written into the buffer or E\_DOES\_NOT\_EXIST if there is no field matching the given key. int persist\_read\_string(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, char \* buffer, const [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) buffer\_size) Reads a string for a given key from persistent storage into a given buffer. The string will be null terminated. If the value has not yet been set, the given buffer is left unchanged. #### Parameters key The key of the field to read from. buffer The pointer to a buffer to be written to. buffer\_size The maximum size of the given buffer. This includes the null character. #### Returns The number of bytes written into the buffer or E\_DOES\_NOT\_EXIST if there is no field matching the given key. [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t) persist\_write\_bool(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const bool value) Writes a bool value flag for a given key into persistent storage. #### Parameters key The key of the field to write to. value The boolean value to write. #### Returns The number of bytes written if successful, a value from [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode) otherwise. [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t) persist\_write\_int(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const int32\_t value) Writes an int value for a given key into persistent storage. ##### Note The int is a signed 32-bit integer. #### Parameters key The key of the field to write to. value The int value to write. #### Returns The number of bytes written if successful, a value from [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode) otherwise. int persist\_write\_data(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const void \* data, const [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) size) Writes a blob of data of a specified size in bytes for a given key into persistent storage. The maximum size is [PERSIST\_DATA\_MAX\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_DATA_MAX_LENGTH). #### Parameters key The key of the field to write to. data The pointer to the blob of data. size The size in bytes. #### Returns The number of bytes written if successful, a value from [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode) otherwise. int persist\_write\_string(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key, const char \* cstring) Writes a string a given key into persistent storage. The maximum size is [PERSIST\_STRING\_MAX\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_STRING_MAX_LENGTH) including the null terminator. #### Parameters key The key of the field to write to. cstring The pointer to null terminated string. #### Returns The number of bytes written if successful, a value from [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode) otherwise. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t) persist\_delete(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Deletes the value of a key from persistent storage. #### Parameters key The key of the field to delete from. [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t) persist\_delete(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Deletes the value of a key from persistent storage. #### Parameters key The key of the field to delete from. [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t) persist\_delete(const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) key) Deletes the value of a key from persistent storage. #### Parameters key The key of the field to delete from. #### Returns S\_TRUE if successful, E\_DOES\_NOT\_EXIST if a value was not set, or another error value from [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode). [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) persist\_get\_max\_size(void) Gets the maximum total size in bytes of all persisted values for the current app on this firmware. Apps targeting older SDKs that don't have this function should assume a 4 KB limit. #### Returns The per-app persistent storage capacity in bytes. ## Enum Documentation enum StatusCode Status codes. See [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t). #### Enumerators S\_SUCCESS Operation completed successfully. E\_ERROR An error occurred (no description). E\_UNKNOWN No idea what went wrong. E\_INTERNAL There was a generic internal logic error. E\_INVALID\_ARGUMENT The function was not called correctly. E\_OUT\_OF\_MEMORY Insufficient allocatable memory available. E\_OUT\_OF\_STORAGE Insufficient long-term storage available. E\_OUT\_OF\_RESOURCES Insufficient resources available. E\_RANGE Argument out of range (may be dynamic). E\_DOES\_NOT\_EXIST Target of operation does not exist. E\_INVALID\_OPERATION Operation not allowed (may depend on state). E\_BUSY Another operation prevented this one. E\_AGAIN Operation not completed; try again. S\_TRUE Equivalent of boolean true. S\_FALSE Equivalent of boolean false. S\_NO\_MORE\_ITEMS For list-style requests. At end of list. S\_NO\_ACTION\_REQUIRED No action was taken as none was required. ## Typedef Documentation typedef int32\_t status\_t Return value for system operations. See [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode) for possible values. ## Macro Definition Documentation #define PERSIST\_DATA\_MAX\_LENGTH 256 The maximum size of a persist value in bytes. #define PERSIST\_STRING\_MAX\_LENGTH [PERSIST\_DATA\_MAX\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_DATA_MAX_LENGTH) The maximum size of a persist string in bytes including the NULL terminator. --- # String Source: https://developer.repebble.com/docs/c/Standard_C/String/ Standard C-string manipulation. ## Function Documentation int strcmp(const char \* str1, const char \* str2) Compares the null terminated strings str1 and str2 to each other. #### Parameters str1 The first C string to compare str2 The second C string to compare #### Returns The difference of the first differing pair of bytes or 0 if the strings are identical int strncmp(const char \* str1, const char \* str2, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n) Compares the null terminated strings str1 and str2 to each other for up to n bytes. Comparison ends when a null is read or when n bytes are read, whichever happens first. #### Parameters str1 The first C string to compare str2 The second C string to compare n The maximum number of bytes to compare #### Returns The difference of the first differing pair of bytes or the final pair of bytes read or 0 if the portions of the strings read are identical char \* strcpy(char \* dest, const char \* src) Copies the string in src into dest and null terminates dest. There should be no overlap of dest and src in memory. #### Parameters dest The destination buffer with enough space for src src The source C string #### Returns The destination buffer dest char \* strncpy(char \* dest, const char \* src, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n) Copies up to n bytes from the string in src into dest and null terminates dest. If dest is null terminated before n bytes have been written, null bytes will continue to be written until n bytes total were written. There should be no overlap of dest and src in memory. #### Parameters dest The destination buffer with enough space for n bytes src The source string n The number of bytes to copy #### Returns The destination buffer dest char \* strcat(char \* dest, const char \* src) Concatenates the string in src to the end of the string pointed by dest and null terminates dest. There should be no overlap of dest and src. #### Parameters dest The destination buffer with enough space for src beyond the null character src The source C string #### Returns The destination buffer dest char \* strncat(char \* dest, const char \* src, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) n) Concatenates up to n bytes from the string in src to the end of the string pointed by dest and null terminates dest. There should be no overlap of dest and src in memeory. #### Parameters dest The destination buffer with enough space for src beyond the null character src The source string n The maximum number of bytes to copy #### Returns The destination buffer dest [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) strlen(const char \* str) Calculates the length of a null terminated string. #### Parameters str The C string. #### Returns The length of the C string str. --- # TextLayer Source: https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer/ Layer that displays and formats a text string. ![](https://developer.repebble.com/assets/images/docs/c/aplite/text_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/text_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/text_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/text_layer.png) ![](https://developer.repebble.com/assets/images/docs/c/aplite/text_layer.png) The geometric information (bounds, frame) of the Layer is used as the "box" in which the text is drawn. The [TextLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md) also has a number of other properties that influence how the text is drawn. Most important of these properties are: a pointer to the string to draw itself, the font, the text color, the background color of the layer, the overflow mode and alignment of the text inside the layer. ## Function Documentation TextLayer \* text\_layer\_create([GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) frame) Creates a new TextLayer on the heap and initializes it with the default values. - Font: Raster Gothic 14-point Boldface (system font) - Text Alignment: GTextAlignmentLeft - Text color: [GColorBlack](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlack) - Background color: [GColorWhite](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorWhite) - Clips: `true` - Hidden: `false` - Caching: `false` The text layer is automatically marked dirty after this operation. #### Parameters frame The frame with which to initialze the TextLayer #### Returns A pointer to the TextLayer. `NULL` if the TextLayer could not be created void text\_layer\_destroy(TextLayer \* text\_layer) Destroys a TextLayer previously created by text\_layer\_create. Layer \* text\_layer\_get\_layer(TextLayer \* text\_layer) Gets the "root" Layer of the text layer, which is the parent for the sub- layers used for its implementation. #### Parameters text\_layer Pointer to the TextLayer for which to get the "root" Layer #### Returns The "root" Layer of the text layer. void text\_layer\_set\_text(TextLayer \* text\_layer, const char \* text) Sets the pointer to the string where the TextLayer is supposed to find the text at a later point in time, when it needs to draw itself. ##### Note The string is not copied, so its buffer most likely cannot be stack allocated, but is recommended to be a buffer that is long-lived, at least as long as the TextLayer is part of a visible Layer hierarchy. #### Parameters text\_layer The TextLayer of which to set the text text The new text to set onto the TextLayer. This must be a null-terminated and valid UTF-8 string! #### See Also [text\_layer\_get\_text](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_get_text) const char \* text\_layer\_get\_text(TextLayer \* text\_layer) Gets the pointer to the string that the TextLayer is using. #### Parameters text\_layer The TextLayer for which to get the text #### See Also [text\_layer\_set\_text](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text) void text\_layer\_set\_background\_color(TextLayer \* text\_layer, GColor color) Sets the background color of the bounding box that will be drawn behind the text. #### Parameters text\_layer The TextLayer of which to set the background color color The new GColor to set the background to #### See Also [text\_layer\_set\_text\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text_color) void text\_layer\_set\_text\_color(TextLayer \* text\_layer, GColor color) Sets the color of text that will be drawn. #### Parameters text\_layer The TextLayer of which to set the text color color The new GColor to set the text color to #### See Also [text\_layer\_set\_background\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_background_color) void text\_layer\_set\_overflow\_mode(TextLayer \* text\_layer, [GTextOverflowMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowMode) line\_mode) Sets the line break mode of the TextLayer. #### Parameters text\_layer The TextLayer of which to set the overflow mode line\_mode The new [GTextOverflowMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowMode) to set void text\_layer\_set\_font(TextLayer \* text\_layer, [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) font) Sets the font of the TextLayer. #### Parameters text\_layer The TextLayer of which to set the font font The new [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) for the TextLayer #### See Also [fonts\_get\_system\_font](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_get_system_font) [fonts\_load\_custom\_font](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_load_custom_font) void text\_layer\_set\_text\_alignment(TextLayer \* text\_layer, [GTextAlignment](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignment) text\_alignment) Sets the alignment of the TextLayer. #### Parameters text\_layer The TextLayer of which to set the alignment text\_alignment The new text alignment for the TextLayer #### See Also [GTextAlignment](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignment) void text\_layer\_enable\_screen\_text\_flow\_and\_paging(TextLayer \* text\_layer, uint8\_t inset) Enables text flow following the boundaries of the screen and pagination that introduces extra line spacing at page breaks to avoid partially clipped lines for the TextLayer. If the TextLayer is part of a [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md) the ScrollLayer's frame will be used to configure paging. ##### Note Make sure the TextLayer is part of the view hierarchy before calling this function. Otherwise it has no effect. #### Parameters text\_layer The TextLayer for which to enable text flow and paging inset Additional amount of pixels to inset to the inside of the screen for text flow #### See Also [text\_layer\_restore\_default\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_restore_default_text_flow_and_paging) [graphics\_text\_attributes\_enable\_screen\_text\_flow](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_enable_screen_text_flow) [graphics\_text\_attributes\_enable\_paging](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_enable_paging) void text\_layer\_restore\_default\_text\_flow\_and\_paging(TextLayer \* text\_layer) Restores text flow and paging for the TextLayer to the rectangular defaults. #### Parameters text\_layer The TextLayer for which to restore text flow and paging #### See Also [text\_layer\_enable\_screen\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_enable_screen_text_flow_and_paging) [graphics\_text\_attributes\_restore\_default\_text\_flow](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_restore_default_text_flow) [graphics\_text\_attributes\_restore\_default\_paging](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_restore_default_paging) [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) text\_layer\_get\_content\_size(TextLayer \* text\_layer) Calculates the size occupied by the current text of the TextLayer. #### Parameters text\_layer the TextLayer for which to calculate the text's size #### Returns The size occupied by the current text of the TextLayer void text\_layer\_set\_size(TextLayer \* text\_layer, const [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) max\_size) Update the size of the text layer This is a convenience function to update the frame of the TextLayer. #### Parameters text\_layer The TextLayer of which to set the size max\_size The new size for the TextLayer ## Typedef Documentation typedef struct TextLayer TextLayer --- # TickTimerService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService/ Handling time components The TickTimerService allows your app to be called every time one Time component has changed. This is extremely important for watchfaces. Your app can choose on which time component change a tick should occur. Time components are defined by a [TimeUnits](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TimeUnits) enum bitmask. ## Function Documentation void tick\_timer\_service\_subscribe([TimeUnits](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TimeUnits) tick\_units, [TickHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler) handler) Subscribe to the tick timer event service. Once subscribed, the handler gets called on every requested unit change. Calling this function multiple times will override the units and handler (i.e., only  the last tick\_units and handler passed will be used). #### Parameters handler The callback to be executed on tick events tick\_units a bitmask of all the units that have changed void tick\_timer\_service\_unsubscribe(void) Unsubscribe from the tick timer event service. Once unsubscribed, the previously registered handler will no longer be called. ## Enum Documentation enum TimeUnits Time unit flags that can be used to create a bitmask for use in [tick\_timer\_service\_subscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#tick_timer_service_subscribe). This will also be passed to [TickHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler). #### Enumerators SECOND\_UNIT Flag to represent the "seconds" time unit. MINUTE\_UNIT Flag to represent the "minutes" time unit. HOUR\_UNIT Flag to represent the "hours" time unit. DAY\_UNIT Flag to represent the "days" time unit. MONTH\_UNIT Flag to represent the "months" time unit. YEAR\_UNIT Flag to represent the "years" time unit. ## Typedef Documentation typedef void(\* TickHandler)(struct tm \*tick\_time, TimeUnits units\_changed) Callback type for tick timer events. #### Parameters tick\_time the time at which the tick event was triggered units\_changed which unit change triggered this tick event --- # Time Source: https://developer.repebble.com/docs/c/Standard_C/Time/ Standard system time functions This module contains standard time functions and formatters for printing. Note that Pebble now supports both local time and UTC time (including timezones and daylight savings time). Most of these functions are part of the C standard library which is documented at [https://sourceware.org/newlib/libc.html#Timefns](https://sourceware.org/newlib/libc.html#Timefns) ## Function Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) int strftime(char \* s, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) maxsize, const char \* format, const struct tm \* tm\_p) Format the time value at tm according to fmt and place the result in a buffer s of size max. #### Parameters s A preallocation char array of size max maxsize the size of the array s format a formatting string tm\_p A pointer to a struct tm containing a broken out time value #### Returns The number of bytes placed in the array s, not including the null byte, 0 if the value does not fit. int strftime(char \* s, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) maxsize, const char \* format, const struct tm \* tm\_p) Format the time value at tm according to fmt and place the result in a buffer s of size max. #### Parameters s A preallocation char array of size max maxsize the size of the array s format a formatting string tm\_p A pointer to a struct tm containing a broken out time value #### Returns The number of bytes placed in the array s, not including the null byte, 0 if the value does not fit. [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) strftime(char \* s, [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) maxsize, const char \* format, const struct tm \* tm\_p) Format the time value at tm according to fmt and place the result in a buffer s of size max. #### Parameters s A preallocation char array of size max maxsize the size of the array s format a formatting string tm\_p A pointer to a struct tm containing a broken out time value #### Returns The number of bytes placed in the array s, not including the null byte, 0 if the value does not fit. struct tm \* localtime(const [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* timep) convert the time value pointed at by clock to a struct tm which contains the time adjusted for the local timezone #### Parameters timep A pointer to an object of type [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) that contains a time value #### Returns A pointer to a struct tm containing the broken out time value adjusted for the local timezone struct tm \* gmtime(const [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* timep) convert the time value pointed at by clock to a struct tm which contains the time expressed in Coordinated Universal Time (UTC) #### Parameters timep A pointer to an object of type [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) that contains a time value #### Returns A pointer to a struct tm containing Coordinated Universal Time (UTC) [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) mktime(struct tm \* tb) convert the broken-down time structure to a timestamp expressed in Coordinated Universal Time (UTC) #### Parameters tb A pointer to an object of type tm that contains broken-down time #### Returns The number of seconds since epoch, January 1st 1970 [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* tloc) Obtain the number of seconds since epoch. Note that the epoch is not adjusted for Timezones and Daylight Savings. #### Parameters tloc Optionally points to an address of a [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable to store the time in. If you only want to use the return value, you may pass NULL into tloc instead #### Returns The number of seconds since epoch, January 1st 1970 double difftime([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) end, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) beginning) Obtain the number of seconds elapsed between beginning and end represented as a double. ##### Note Pebble uses software floating point emulation. Including this function which returns a double will significantly increase the size of your binary. We recommend directly subtracting both timestamps to calculate a time difference. ``` int difference = ts1 - ts2; ``` #### Parameters end A [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable representing some number of seconds since epoch, January 1st 1970 beginning A [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable representing some number of seconds since epoch, January 1st 1970. Note that end should be greater than beginning, but this is not enforced. #### Returns The number of seconds elapsed between beginning and end. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) time\_ms([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* t\_utc, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) \* out\_ms) Obtain the number of seconds and milliseconds part since the epoch. This is a non-standard C function provided for convenience. #### Parameters tloc Optionally points to an address of a [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable to store the time in. You may pass NULL into tloc if you don't need a [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable to be set with the seconds since the epoch out\_ms Optionally points to an address of a [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) variable to store the number of milliseconds since the last second in. If you only want to use the return value, you may pass NULL into out\_ms instead #### Returns The number of milliseconds since the last second [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) time\_ms([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* t\_utc, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) \* out\_ms) Obtain the number of seconds and milliseconds part since the epoch. This is a non-standard C function provided for convenience. #### Parameters tloc Optionally points to an address of a [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable to store the time in. You may pass NULL into tloc if you don't need a [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable to be set with the seconds since the epoch out\_ms Optionally points to an address of a [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) variable to store the number of milliseconds since the last second in. If you only want to use the return value, you may pass NULL into out\_ms instead #### Returns The number of milliseconds since the last second [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) time\_ms([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* tloc, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) \* out\_ms) Obtain the number of seconds and milliseconds part since the epoch. This is a non-standard C function provided for convenience. #### Parameters tloc if provided receives the current UTC Unix Time seconds portion out\_ms if provided receives the current Unix Time milliseconds portion #### Returns Current Unix Time milliseconds portion [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) time\_start\_of\_today(void) Return the UTC time that corresponds to the start of today (midnight). #### Returns the UTC time corresponding to the start of today (midnight) ## Typedef Documentation typedef unsigned int time\_t time in seconds since the epoch, January 1st 1970 ## Macro Definition Documentation #define TZ\_LEN 6 #define SECONDS\_PER\_MINUTE (60) #define MINUTES\_PER\_HOUR (60) #define SECONDS\_PER\_HOUR (SECONDS\_PER\_MINUTE \* MINUTES\_PER\_HOUR) #define HOURS\_PER\_DAY (24) #define MINUTES\_PER\_DAY (HOURS\_PER\_DAY \* MINUTES\_PER\_HOUR) #define SECONDS\_PER\_DAY (MINUTES\_PER\_DAY \* SECONDS\_PER\_MINUTE) --- # Timer Source: https://developer.repebble.com/docs/c/Foundation/Timer/ Can be used to execute some code at some point in the future. ## Function Documentation void psleep(int millis) Waits for a certain amount of milliseconds. #### Parameters millis The number of milliseconds to wait for AppTimer \* app\_timer\_register([uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) timeout\_ms, [AppTimerCallback](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimerCallback) callback, void \* callback\_data) Registers a timer that ends up in callback being called some specified time in the future. #### Parameters timeout\_ms The expiry time in milliseconds from the current time callback The callback that gets called at expiry time callback\_data The data that will be passed to callback #### Returns A pointer to an `AppTimer` that can be used to later reschedule or cancel this timer - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) bool app\_timer\_reschedule(AppTimer \* timer\_handle, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) new\_timeout\_ms) Reschedules an already running timer for some point in the future. #### Parameters timer\_handle The timer to reschedule new\_timeout\_ms The new expiry time in milliseconds from the current time #### Returns true if the timer was rescheduled, false if the timer has already elapsed bool app\_timer\_reschedule(AppTimer \* timer\_handle, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) new\_timeout\_ms) Reschedules an already running timer for some point in the future. #### Parameters timer\_handle The timer to reschedule new\_timeout\_ms The new expiry time in milliseconds from the current time #### Returns true if the timer was rescheduled, false if the timer has already elapsed bool app\_timer\_reschedule(AppTimer \* timer\_handle, [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) new\_timeout\_ms) Reschedules an already running timer for some point in the future. Elapsed timers cannot be rescheduled. #### Parameters timer\_handle The timer to reschedule new\_timeout\_ms The new expiry time in milliseconds from the current time #### Returns true if the timer was rescheduled, false if the timer has already elapsed - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void app\_timer\_cancel(AppTimer \* timer\_handle) Cancels an already registered timer. Once cancelled the handle may no longer be used for any purpose. void app\_timer\_cancel(AppTimer \* timer\_handle) Cancels an already registered timer. Once cancelled the handle may no longer be used for any purpose. void app\_timer\_cancel(AppTimer \* timer\_handle) Cancels an already registered timer. Once cancelled the handle may no longer be used for any purpose. Elapsed timers do not need to be cancelled. ## Typedef Documentation typedef struct AppTimer AppTimer typedef void(\* AppTimerCallback)(void \*data) The type of function which can be called when a timer fires. The argument will be the `callback_data` passed to [app\_timer\_register()](https://developer.repebble.com/docs/c/Foundation/Timer.md#app_timer_register). --- # TouchService Source: https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService/ The APIs on this page will only work with SDK 4.9+. ## Function Documentation void touch\_service\_subscribe([TouchServiceHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchServiceHandler) handler, void \* context) Subscribe to touch events. The touch sensor is enabled while subscribed. #### Parameters handler Callback invoked for each touch event context User context passed to the callback void touch\_service\_unsubscribe(void) Unsubscribe from touch events. The touch sensor is disabled if no other subscribers remain. bool touch\_service\_is\_enabled(void) #### Returns true if touch input is currently being delivered to apps. Returns false on platforms without a touchscreen, or when touch has been disabled system-wide (future feature). Apps can poll this (for example on window appear) to avoid looking broken when touch is unavailable. void app\_touch\_navigation\_enable(bool enable) Opt this app into (or out of) touch navigation: the system gesture bridge that maps swipes/taps on a MenuLayer/ScrollLayer to button presses. Third-party apps are opt-out by default and receive no touch navigation even when the user has enabled it system-wide; call this with _enable_ true to participate. Takes effect immediately when touch navigation is enabled system-wide, otherwise once the user enables it. #### Parameters enable true to participate in touch navigation, false to stop participating. ## Data Structure Documentation struct TouchEvent Touch event data, carried directly in PebbleTouchEvent. #### Data Fields [TouchEventType](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEventType) typebool non\_navigational true when the touch must not drive navigation: the interaction session was inactive at Touchdown (unarmed contact on the idle watchface). Latched on Touchdown and carried across the whole gesture. int16\_t xint16\_t y ## Enum Documentation enum TouchEventType Touch event type. #### Enumerators TouchEvent\_TouchdownTouchEvent\_LiftoffTouchEvent\_PositionUpdate ## Typedef Documentation typedef void(\* TouchServiceHandler)(const TouchEvent \*event, void \*context) Callback for touch events. #### Parameters event The touch event data context User-provided context --- # UnobstructedArea Source: https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea/ Events about when the app's unobstructed area changes for visually adapting Unobstructed Area enables a watchface to adapt to overlays partially obstructing it. Timeline Peek is the only overlay, and it partially obstructs the bottom of watchfaces, displaying the immediately upcoming or newly began event. Unobstructed Area is for use only with watchfaces. There will be no Unobstructed Area events emitted for apps that are not watchfaces. Timeline Peek is also limited to rectangular platforms, thus using Unobstructed Area on Chalk will also result in no events. Watchfaces are encouraged to respect Unobstructed Area in order to dynamically resize within the remaining screen area that isn't obstructed by an overlay. Unobstructed Area provides handlers for when the screen area will begin and end changing as well as progress for every frame of the animation, allowing one to animate the watchface to resize while the overlay animates into or out of the screen. Additionally, at any point, a watchface can query the Unobstructed Area using [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds) even if there was not event or if it didn't subscribe to them. The simplest usage of Unobstructed Area is to change rendering of your watchface to render within the unobstructed bounds obtained by [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds). This is accomplished by modifying your layer update procedure that you setup a layer with using [layer\_set\_update\_proc](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_update_proc) to use [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds) instead of [layer\_get\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds) or [layer\_get\_frame](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_frame) and adjusting all the rendering logic within it to handle resizing bounds. The app will redraw automatically whenever the Unobstructed Area changes, even if you did not subscribe to the events. Subscribing to `will_change`, `change`, and `did_change` events with app\_unobstructed\_area\_service\_subscribe will allow you to obtain the final unobstructed area as soon as the animation begins and the current animation progress for each frame as they occur should you need that information to resize your watchface. When designing your watchface resizing logic or animation, take into consideration the way Timeline Peek animates. Timeline Peek appears from the bottom with an animation that moves slightly at first and snaps across with a bounce back. Because there is a bounce back in the animation, there will be momentary frames where the unobstructed area is slightly smaller than when Timeline Peek is visible but not animating. If you intend to hide or show layers on the screen depending on the unobstructed area, there are two ways to do this. For anything inside a layer update procedure the simple solution would be sufficient. That is, in your update procedure, you can to determine whether something is to be rendered or not based on the remaining height available to you determined from [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds). The other method for deciding whether to show or hide layers is to subscribe to events using app\_unobstructed\_area\_service\_subscribe. Note that hiding or showing elements in your `will_change` can be too early due to the nature of the Timeline Peek animation where it moves slowly in the first few frames. After judging the animation visually, you may instead show or hide your layers based on both the final unobstructed area obtained from `will_change` in combination with the progress obtained from `change`, or check the current height available to you by calling [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds) in your `change` handler to update your state. For more advance usages such as functionally defined animations whose animation frames are a function of animation progress, you will want to subscribe to `will_change`, `change`, and optionally `did_change` events if you need to clean up resources or layers created in your `will_change` handler. For example, in your `will_change` handler, you may use [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds) to get the current unobstructed area, and combine it with the final unobstructed area passed into `will_change` to set up a [PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md). A separate example would be to save the animation progress passed to your `change` handler for use in your own custom easing curve in your layer update procedure. ## Function Documentation void unobstructed\_area\_service\_subscribe(UnobstructedAreaHandlers handlers, void \* context) Subscribe to be notified when the app's unobstructed area changes. When an unobstructed area begins changing, the `will_change` handler will be called, and every `will_change` call is always paired with a `did_change` call that occurs when it is done changing given that the `will_change` and `did_change` handlers are set. When subscribing while the unobstructed area is changing, the `will_change` handler will be called after subscription in the next event loop. #### Parameters handlers The handlers that should be called when the unobstructed area changes. context A user-provided context that will be passed to the callback handlers. #### See Also [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds) void unobstructed\_area\_service\_unsubscribe(void) Unsubscribe from notifications about changes to the app's unobstructed area. ## Data Structure Documentation struct UnobstructedAreaHandlers #### Data Fields [UnobstructedAreaWillChangeHandler](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaWillChangeHandler) will\_change Handler that will be called just before the unobstructed area will begin changing. [UnobstructedAreaChangeHandler](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaChangeHandler) change Handler that will be called every time the unobstructed area changes. [UnobstructedAreaDidChangeHandler](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaDidChangeHandler) did\_change Handler that will be called after the unobstructed area has finished changing. ## Typedef Documentation typedef void(\* UnobstructedAreaWillChangeHandler)(GRect final\_unobstructed\_screen\_area, void \*context) Handler that will be called just before the unobstructed area will begin changing. #### Parameters final\_unobstructed\_screen\_area The final unobstructed screen area after the unobstructed area has finished changing. context A user-provided context. typedef void(\* UnobstructedAreaChangeHandler)(AnimationProgress progress, void \*context) Handler that will be called every time the unobstructed area changes. #### Parameters progress The progress of the animation changing the unobstructed area. context A user-provided context. typedef void(\* UnobstructedAreaDidChangeHandler)(void \*context) Handler that will be called after the unobstructed area has finished changing. #### Parameters context A user-provided context. --- # User Interface Source: https://developer.repebble.com/docs/c/User_Interface/ Everything related to user interface. The User Interface framework enables your Pebble watchface or watchapp to handle basic UI components, like clicks, layers, vibes, windows and window stacks. ## Modules #### [Animation](https://developer.repebble.com/docs/c/User_Interface/Animation.md) Abstract framework to create arbitrary animations #### [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) Handling button click interactions #### [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) Low-level touch-gesture recognizers for building fully custom touch interactions on touch-capable hardware. #### [Layers](https://developer.repebble.com/docs/c/User_Interface/Layers.md) User interface layers for displaying graphic components #### [Light](https://developer.repebble.com/docs/c/User_Interface/Light.md) Controlling Pebble's backlight #### [Preferences](https://developer.repebble.com/docs/c/User_Interface/Preferences.md)   #### [Speaker](https://developer.repebble.com/docs/c/User_Interface/Speaker.md) Controlling the speaker #### [UnobstructedArea](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md)   #### [Vibes](https://developer.repebble.com/docs/c/User_Interface/Vibes.md) Controlling the vibration motor #### [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md) The basic building block of the user interface #### [Window Stack](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md) The multiple window manager --- # UUID Source: https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID/ ## Function Documentation bool uuid\_equal(const Uuid \* uu1, const Uuid \* uu2) Compares two UUIDs. #### Returns True if the two UUIDs are equal, false if they are not. void uuid\_to\_string(const Uuid \* uuid, char \* buffer) Writes UUID in a string form into buffer that looks like the following... {12345678-1234-5678-1234-567812345678} or {NULL UUID} if NULL was passed. #### Parameters uuid The Uuid to write into the buffer as human-readable string buffer Memory to write the string to. Must be at least [UUID\_STRING\_BUFFER\_LENGTH](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#UUID_STRING_BUFFER_LENGTH) bytes long. ## Data Structure Documentation struct Uuid #### Data Fields uint8\_t byte0uint8\_t byte1uint8\_t byte2uint8\_t byte3uint8\_t byte4uint8\_t byte5uint8\_t byte6uint8\_t byte7uint8\_t byte8uint8\_t byte9uint8\_t byte10uint8\_t byte11uint8\_t byte12uint8\_t byte13uint8\_t byte14uint8\_t byte15 ## Macro Definition Documentation #define UUID\_SIZE 16 #define UuidMake ( p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15) Make a Uuid object from sixteen bytes. #### Returns A Uuid structure representing the bytes p0 to p15. #define UuidMakeFromBEBytes ( b) Creates a Uuid from an array of bytes with 16 bytes in Big Endian order. #### Returns The created Uuid #define UuidMakeFromLEBytes ( b) Creates a Uuid from an array of bytes with 16 bytes in Little Endian order. #### Returns The created Uuid #define UUID\_STRING\_BUFFER\_LENGTH (32 + 4 + 2 + 1) The minimum required length of a string used to hold a uuid (including null). --- # Vibes Source: https://developer.repebble.com/docs/c/User_Interface/Vibes/ Controlling the vibration motor The Vibes API provides calls that let you control Pebble’s vibration motor. The vibration motor can be used as a visceral mechanism for giving immediate feedback to the user. You can use it to highlight important moments in games, or to draw the attention of the user. However, you should use the vibration feature sparingly, because sustained use will rapidly deplete Pebble’s battery, and vibrating Pebble too much and too often can become annoying for users. ## Function Documentation void vibes\_cancel(void) Cancel any in-flight vibe patterns; this is a no-op if there is no on-going vibe. void vibes\_short\_pulse(void) Makes the watch emit one short vibration. void vibes\_long\_pulse(void) Makes the watch emit one long vibration. void vibes\_double\_pulse(void) Makes the watch emit two brief vibrations. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void vibes\_enqueue\_custom\_pattern([VibePattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#VibePattern) pattern) Makes the watch emit a ‘custom’ vibration pattern. #### Parameters pattern An arbitrary vibration pattern #### See Also [VibePattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#VibePattern) void vibes\_enqueue\_custom\_pattern([VibePattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#VibePattern) pattern) Makes the watch emit a ‘custom’ vibration pattern. #### Parameters pattern An arbitrary vibration pattern #### See Also [VibePattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#VibePattern) void vibes\_enqueue\_custom\_pattern([VibePattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#VibePattern) pattern) Makes the watch emit a 'custom' vibration pattern. #### Parameters pattern An arbitrary vibration pattern #### See Also [VibePattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#VibePattern) ## Data Structure Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) struct VibePattern Data structure describing a vibration pattern. A pattern consists of at least 1 vibe-on duration, optionally followed by alternating vibe-off + vibe-on durations. Each segment may have a different duration. Example code: ``` // Vibe pattern: ON for 200ms, OFF for 100ms, ON for 400ms:static const uint32\_t const segments[] = { 200, 100, 400 };VibePattern pat = {.durations = segments,.num\_segments = ARRAY\_LENGTH(segments),};vibes\_enqueue\_custom\_pattern(pat); ``` #### Data Fields const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* durations Pointer to an array of segment durations, measured in milli-seconds. The maximum allowed duration is 10000ms. [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_segments The length of the array of durations. #### See Also [vibes\_enqueue\_custom\_pattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_enqueue_custom_pattern) struct VibePattern Data structure describing a vibration pattern. A pattern consists of at least 1 vibe-on duration, optionally followed by alternating vibe-off + vibe-on durations. Each segment may have a different duration. Example code: ``` // Vibe pattern: ON for 200ms, OFF for 100ms, ON for 400ms:static const uint32\_t const segments[] = { 200, 100, 400 };VibePattern pat = {.durations = segments,.num\_segments = ARRAY\_LENGTH(segments),};vibes\_enqueue\_custom\_pattern(pat); ``` #### Data Fields const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* durations Pointer to an array of segment durations, measured in milli-seconds. The maximum allowed duration is 10000ms. [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_segments The length of the array of durations. #### See Also [vibes\_enqueue\_custom\_pattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_enqueue_custom_pattern) struct VibePattern Data structure describing a vibration pattern. A pattern consists of at least 1 vibe-on duration, optionally followed by alternating vibe-off + vibe-on durations. Each segment may have a different duration. Example code: ``` // Vibe pattern: ON for 200ms, OFF for 100ms, ON for 400ms:static const uint32\_t segments[] = { 200, 100, 400 };VibePattern pat = {.durations = segments,.num\_segments = ARRAY\_LENGTH(segments),};vibes\_enqueue\_custom\_pattern(pat); ``` #### Data Fields const [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) \* durations Pointer to an array of segment durations, measured in milli-seconds. The maximum allowed duration is 10000ms. [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) num\_segments The length of the array of durations. #### See Also [vibes\_enqueue\_custom\_pattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_enqueue_custom_pattern) --- # Wakeup Source: https://developer.repebble.com/docs/c/Foundation/Wakeup/ Allows applications to schedule to be launched even if they are not running. ## Function Documentation - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) void wakeup\_service\_subscribe([WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) handler) Registers a [WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) to be called when wakeup events occur. #### Parameters handler The callback that gets called when the wakeup event occurs void wakeup\_service\_subscribe([WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) handler) Registers a [WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) to be called when wakeup events occur. #### Parameters handler The callback that gets called when the wakeup event occurs void wakeup\_service\_subscribe([WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) handler) Registers a [WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) to be called when wakeup events occur. ##### Note The handler is only called for wakeup events which occur while the app is already running; use `launch_reason()` === APP\_LAUNCH\_WAKEUP to detect when the app was launched by a wakeup event. #### Parameters handler The callback that gets called when the wakeup event occurs [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) wakeup\_schedule([time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) timestamp, int32\_t cookie, bool notify\_if\_missed) Registers a wakeup event that triggers a callback at the specified time. Applications may only schedule up to 8 wakeup events. Wakeup events are given a 1 minute duration window, in that no application may schedule a wakeup event with 1 minute of a currently scheduled wakeup event. #### Parameters timestamp The requested time (UTC) for the wakeup event to occur cookie The application specific reason for the wakeup event notify\_if\_missed On powering on Pebble, will alert user when notifications were missed due to Pebble being off. #### Returns negative values indicate errors ([StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode)) E\_RANGE if the event cannot be scheduled due to another event in that period. E\_INVALID\_ARGUMENT if the time requested is in the past. E\_OUT\_OF\_RESOURCES if the application has already scheduled all 8 wakeup events. E\_INTERNAL if a system error occurred during scheduling. void wakeup\_cancel([WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) wakeup\_id) Cancels a wakeup event. #### Parameters wakeup\_id Wakeup event to cancel void wakeup\_cancel\_all(void) Cancels all wakeup event for the app. bool wakeup\_get\_launch\_event([WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) \* wakeup\_id, int32\_t \* cookie) Retrieves the wakeup event info for an app that was launched by a wakeup\_event (ie. `launch_reason()` === APP\_LAUNCH\_WAKEUP) so that an app may display information regarding the wakeup event. #### Parameters wakeup\_id [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) for the wakeup event that caused the app to wakeup cookie App provided reason for the wakeup event #### Returns True if app was launched due to a wakeup event, false otherwise bool wakeup\_query([WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) wakeup\_id, [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) \* timestamp) Checks if the current [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) is still scheduled and therefore valid. #### Parameters wakeup\_id Wakeup event to query for validity and scheduled time timestamp Optionally points to an address of a [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) variable to store the time that the wakeup event is scheduled to occur. (The time is in UTC, but local time when [clock\_is\_timezone\_set](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_is_timezone_set) returns false). You may pass NULL instead if you do not need it. #### Returns True if [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) is still scheduled, false if it doesn't exist or has already occurred ## Typedef Documentation typedef int32\_t WakeupId [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) is an identifier for a wakeup event. typedef void(\* WakeupHandler)(WakeupId wakeup\_id, int32\_t cookie) The type of function which can be called when a wakeup event occurs. The arguments will be the id of the wakeup event that occurred, as well as the scheduled cookie provided to `wakeup_schedule`. --- # Wall Time Source: https://developer.repebble.com/docs/c/Foundation/Wall_Time/ Functions, data structures and other things related to wall clock time. This module contains utilities to get the current time and create strings with formatted dates and times. ## Function Documentation void clock\_copy\_time\_string(char \* buffer, uint8\_t size) Copies a time string into the buffer, formatted according to the user's time display preferences (such as 12h/24h time). Example results: "7:30" or "15:00". ##### Note AM/PM are also outputted with the time if the user's preference is 12h time. #### Parameters buffer (out) A pointer to the buffer to copy the time string into size The maximum size of buffer bool clock\_is\_24h\_style(void) Gets the user's 12/24h clock style preference. #### Returns `true` if the user prefers 24h-style time display or `false` if the user prefers 12h-style time display. - [SDK 3](javascript:void(0);) - [SDK 4](javascript:void(0);) - [SDK 4.9+](javascript:void(0);) [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) clock\_to\_timestamp([WeekDay](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WeekDay) day, int hour, int minute) Converts a (day, hour, minute) specification to a UTC timestamp occurring in the future Always returns a timestamp for the next occurring instance, example: specifying TODAY@14:30 when it is 14:40 will return a timestamp for 7 days from now at 14:30. ##### Note This function does not support Daylight Saving Time (DST) changes, events scheduled during a DST change will be off by an hour. #### Parameters day [WeekDay](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WeekDay) day of week including support for specifying TODAY hour hour specified in 24-hour format [0-23] minute minute [0-59] [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) clock\_to\_timestamp([WeekDay](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WeekDay) day, int hour, int minute) Converts a (day, hour, minute) specification to a UTC timestamp occurring in the future Always returns a timestamp for the next occurring instance, example: specifying TODAY@14:30 when it is 14:40 will return a timestamp for 7 days from now at 14:30. ##### Note This function does not support Daylight Saving Time (DST) changes, events scheduled during a DST change will be off by an hour. #### Parameters day [WeekDay](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WeekDay) day of week including support for specifying TODAY hour hour specified in 24-hour format [0-23] minute minute [0-59] [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) clock\_to\_timestamp([WeekDay](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WeekDay) day, int hour, int minute) Converts a (day, hour, minute) specification to a UTC timestamp occurring in the future Always returns a timestamp for the next occurring instance, example: specifying TODAY@14:30 when it is 14:40 will return a timestamp for 7 days from now at 14:30. #### Parameters day [WeekDay](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WeekDay) day of week including support for specifying TODAY hour hour specified in 24-hour format [0-23] minute minute [0-59] bool clock\_is\_timezone\_set(void) Checks if timezone is currently set, otherwise gmtime == localtime. #### Returns `true` if timezone has been set, false otherwise void clock\_get\_timezone(char \* timezone, const [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) buffer\_size) If timezone is set, copies the current timezone long name (e.g. America/Chicago) to user-provided buffer. ##### Note timezone buffer should be at least TIMEZONE\_NAME\_LENGTH bytes #### Parameters timezone A pointer to the buffer to copy the timezone long name into buffer\_size Size of the allocated buffer to copy the timezone long name into ## Enum Documentation enum WeekDay Weekday values. #### Enumerators TODAY Today. SUNDAY Sunday. MONDAY Monday. TUESDAY Tuesday. WEDNESDAY Wednesday. THURSDAY Thursday. FRIDAY Friday. SATURDAY Saturday. ## Macro Definition Documentation #define TIMEZONE\_NAME\_LENGTH 32 The maximum length for a timezone full name (e.g. America/Chicago) --- # WatchInfo Source: https://developer.repebble.com/docs/c/Foundation/WatchInfo/ Provides information about the watch itself. This API provides access to information such as the watch model, watch color and watch firmware version. ## Function Documentation [WatchInfoModel](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoModel) watch\_info\_get\_model(void) Provides the model of the watch. #### Returns `WatchInfoModel` representing the model of the watch. [WatchInfoVersion](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoVersion) watch\_info\_get\_firmware\_version(void) Provides the version of the firmware running on the watch. #### Returns `WatchInfoVersion` representing the version of the firmware running on the watch. [WatchInfoColor](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoColor) watch\_info\_get\_color(void) ## Data Structure Documentation struct WatchInfoVersion Data structure containing the version of the firmware running on the watch. The version of the firmware has the form X.[X.[X]]. If a version number is not present it will be 0. For example: the version numbers of 2.4.1 are 2, 4, and 1. The version numbers of 2.4 are 2, 4, and 0. #### Data Fields uint8\_t major Major version number. uint8\_t minor Minor version number. uint8\_t patch Patch version number. ## Enum Documentation enum WatchInfoModel The different watch models. #### Enumerators WATCH\_INFO\_MODEL\_UNKNOWN Unknown model. WATCH\_INFO\_MODEL\_PEBBLE\_ORIGINAL Original Pebble. WATCH\_INFO\_MODEL\_PEBBLE\_STEEL Pebble Steel. WATCH\_INFO\_MODEL\_PEBBLE\_TIME Pebble Time. WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_STEEL Pebble Time Steel. WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_ROUND\_14 Pebble Time Round, 14mm lug size. WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_ROUND\_20 Pebble Time Round, 20mm lug size. WATCH\_INFO\_MODEL\_PEBBLE\_2\_HR Pebble 2 HR. WATCH\_INFO\_MODEL\_PEBBLE\_2\_SE Pebble 2 SE. WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_2 Pebble Time 2. WATCH\_INFO\_MODEL\_COREDEVICES\_P2D CoreDevices P2D (Pebble 2 Duo) WATCH\_INFO\_MODEL\_COREDEVICES\_PT2 CoreDevices PT2 (Pebble Time 2) WATCH\_INFO\_MODEL\_COREDEVICES\_PR2 CoreDevices PR2 (Pebble Round 2) WATCH\_INFO\_MODEL\_\_MAX enum WatchInfoColor The different watch colors. #### Enumerators WATCH\_INFO\_COLOR\_UNKNOWN Unknown color. WATCH\_INFO\_COLOR\_BLACK Black. WATCH\_INFO\_COLOR\_WHITE White. WATCH\_INFO\_COLOR\_RED Red. WATCH\_INFO\_COLOR\_ORANGE Orange. WATCH\_INFO\_COLOR\_GRAY Gray. WATCH\_INFO\_COLOR\_STAINLESS\_STEEL Stainless Steel. WATCH\_INFO\_COLOR\_MATTE\_BLACK Matte Black. WATCH\_INFO\_COLOR\_BLUE Blue. WATCH\_INFO\_COLOR\_GREEN Green. WATCH\_INFO\_COLOR\_PINK Pink. WATCH\_INFO\_COLOR\_TIME\_WHITE Time White. WATCH\_INFO\_COLOR\_TIME\_BLACK Time Black. WATCH\_INFO\_COLOR\_TIME\_RED Time Red. WATCH\_INFO\_COLOR\_TIME\_STEEL\_SILVER Time Steel Silver. WATCH\_INFO\_COLOR\_TIME\_STEEL\_BLACK Time Steel Black. WATCH\_INFO\_COLOR\_TIME\_STEEL\_GOLD Time Steel Gold. WATCH\_INFO\_COLOR\_TIME\_ROUND\_SILVER\_14 Time Round 14mm lug size, Silver. WATCH\_INFO\_COLOR\_TIME\_ROUND\_BLACK\_14 Time Round 14mm lug size, Black. WATCH\_INFO\_COLOR\_TIME\_ROUND\_SILVER\_20 Time Round 20mm lug size, Silver. WATCH\_INFO\_COLOR\_TIME\_ROUND\_BLACK\_20 Time Round 20mm lug size, Black. WATCH\_INFO\_COLOR\_TIME\_ROUND\_ROSE\_GOLD\_14 Time Round 14mm lug size, Rose Gold. WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_BLACK Pebble 2 HR, Black / Charcoal. WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_LIME Pebble 2 HR, Charcoal / Sorbet Green. WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_FLAME Pebble 2 HR, Charcoal / Red. WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_WHITE Pebble 2 HR, White / Gray. WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_AQUA Pebble 2 HR, White / Turquoise. WATCH\_INFO\_COLOR\_PEBBLE\_2\_SE\_BLACK Pebble 2 SE, Black / Charcoal. WATCH\_INFO\_COLOR\_PEBBLE\_2\_SE\_WHITE Pebble 2 SE, White / Gray. WATCH\_INFO\_COLOR\_PEBBLE\_TIME\_2\_BLACK Pebble Time 2, Black. WATCH\_INFO\_COLOR\_PEBBLE\_TIME\_2\_SILVER Pebble Time 2, Silver. WATCH\_INFO\_COLOR\_PEBBLE\_TIME\_2\_GOLD Pebble Time 2, Gold. WATCH\_INFO\_COLOR\_COREDEVICES\_P2D\_BLACK CoreDevices P2D, Black. WATCH\_INFO\_COLOR\_COREDEVICES\_P2D\_WHITE CoreDevices P2D, White. WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_BLACK\_GREY CoreDevices PT2, Black/Grey. WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_BLACK\_RED CoreDevices PT2, Black/Red. WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_SILVER\_BLUE CoreDevices PT2, Silver/Blue. WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_SILVER\_GREY CoreDevices PT2, Silver/Grey. WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_BLACK\_20 CoreDevices PR2, Black (20mm) WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_SILVER\_20 CoreDevices PR2, Silver (20mm) WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_GOLD\_14 CoreDevices PR2, Gold (14mm) WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_SILVER\_14 CoreDevices PR2, Silver (14mm) WATCH\_INFO\_COLOR\_\_MAX --- # Window Source: https://developer.repebble.com/docs/c/User_Interface/Window/ The basic building block of the user interface Windows are the top-level elements in the UI hierarchy and the basic building blocks for a Pebble UI. A single window is always displayed at a time on Pebble, with the exception of when animating from one window to the other, which, in that case, is managed by the window stack. You can stack windows on top of each other, but only the topmost window will be visible. Users wearing a Pebble typically interact with the content and media displayed in a window, clicking and pressing buttons on the watch, depending on what they see and wish to respond to in a window. Windows serve to display a hierarchy of layers on the screen and handle user input. When a window is visible, its root Layer (and all its child layers) are drawn onto the screen automatically. You need a window, which always fills the entire screen, to display images, text, and graphics in your Pebble app. A layer by itself doesn’t display on Pebble; it must be in the current window’s layer hierarchy to be visible. The Window Stack serves as the global manager of what window is presented and makes sure that input events are forwarded to the topmost window. Refer to the [User Interface Layers chapter in the Pebble Developer Guides](https://developer.repebble.com/guides/user-interfaces/layers.md) (chapter "Window") for a conceptual overview of Window, the Window Stack and relevant code examples. ## Modules #### [ActionMenu](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md)   #### [NumberWindow](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md) A ready-made Window prompting the user to pick a number ## Function Documentation Window \* window\_create(void) Creates a new Window on the heap and initalizes it with the default values. - Background color : `GColorWhite` - Root layer's `update_proc` : function that fills the window's background using `background_color`. - `click_config_provider` : `NULL` - `window_handlers` : all `NULL` #### Returns A pointer to the window. `NULL` if the window could not be created void window\_destroy(Window \* window) Destroys a Window previously created by window\_create. void window\_set\_click\_config\_provider(Window \* window, [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) click\_config\_provider) Sets the click configuration provider callback function on the window. This will automatically setup the input handlers of the window as well to use the click recognizer subsystem. #### Parameters window The window for which to set the click config provider click\_config\_provider The callback that will be called to configure the click recognizers with the window #### See Also [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) void window\_set\_click\_config\_provider\_with\_context(Window \* window, [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) click\_config\_provider, void \* context) Same as [window\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider), but will assign a custom context pointer (instead of the window pointer) that will be passed into the [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) click event handlers. #### Parameters window The window for which to set the click config provider click\_config\_provider The callback that will be called to configure the click recognizers with the window context Pointer to application specific data that will be passed to the click configuration provider callback (defaults to the window). #### See Also [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) [window\_set\_click\_config\_provider](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider) [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) window\_get\_click\_config\_provider(const Window \* window) Gets the current click configuration provider of the window. #### Parameters window The window for which to get the click config provider void \* window\_get\_click\_config\_context(Window \* window) Gets the current click configuration provider context of the window. #### Parameters window The window for which to get the click config provider context void window\_set\_window\_handlers(Window \* window, [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) handlers) Sets the window handlers of the window. These handlers get called e.g. when the user enters or leaves the window. #### Parameters window The window for which to set the window handlers handlers The handlers for the specified window #### See Also [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) struct Layer \* window\_get\_root\_layer(const Window \* window) Gets the root Layer of the window. The root layer is the layer at the bottom of the layer hierarchy for this window. It is the window's "canvas" if you will. By default, the root layer only draws a solid fill with the window's background color. #### Parameters window The window for which to get the root layer #### Returns The window's root layer void window\_set\_background\_color(Window \* window, GColor background\_color) Sets the background color of the window, which is drawn automatically by the root layer of the window. #### Parameters window The window for which to set the background color background\_color The new background color #### See Also [window\_get\_root\_layer()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_root_layer) bool window\_is\_loaded(Window \* window) Gets whether the window has been loaded. If a window is loaded, its `.load` handler has been called (and the `.unload` handler has not been called since). #### Parameters window The window to query its loaded status #### Returns true if the window is currently loaded or false if not. #### See Also [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) void window\_set\_user\_data(Window \* window, void \* data) Sets a pointer to developer-supplied data that the window uses, to provide a means to access the data at later times in one of the window event handlers. #### Parameters window The window for which to set the user data data A pointer to user data. #### See Also [window\_get\_user\_data](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_user_data) void \* window\_get\_user\_data(const Window \* window) Gets the pointer to developer-supplied data that was previously set using [window\_set\_user\_data()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_user_data). #### Parameters window The window for which to get the user data #### See Also [window\_set\_user\_data](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_user_data) void window\_single\_click\_subscribe([ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) handler) Subscribe to single click events. ##### Notes Must be called from the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider). [window\_single\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe) and [window\_single\_repeating\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_repeating_click_subscribe) conflict, and cannot both be used on the same button. When there is a multi\_click and/or long\_click setup, there will be a delay before the single click #### Parameters button\_id The button events to subscribe to. handler The [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) to fire on this event. handler will get fired. On the other hand, when there is no multi\_click nor long\_click setup, the single click handler will fire directly on button down. #### See Also [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) [window\_single\_repeating\_click\_subscribe](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_repeating_click_subscribe) void window\_single\_repeating\_click\_subscribe([ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) repeat\_interval\_ms, [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) handler) Subscribe to single click event, with a repeat interval. A single click is detected every time "repeat\_interval\_ms" has been reached. ##### Notes Must be called from the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider). [window\_single\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe) and [window\_single\_repeating\_click\_subscribe()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_repeating_click_subscribe) conflict, and cannot both be used on the same button. The back button cannot be overridden with a repeating click. #### Parameters button\_id The button events to subscribe to. repeat\_interval\_ms When holding down, how many milliseconds before the handler is fired again. A value of 0ms means "no repeat timer". The minimum is 30ms, and values below will be disregarded. If there is a long-click handler subscribed on this button, `repeat_interval_ms` will not be used. handler The [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) to fire on this event. #### See Also [window\_single\_click\_subscribe](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe) void window\_multi\_click\_subscribe([ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, uint8\_t min\_clicks, uint8\_t max\_clicks, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) timeout, bool last\_click\_only, [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) handler) Subscribe to multi click events. ##### Note Must be called from the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider). #### Parameters button\_id The button events to subscribe to. min\_clicks Minimum number of clicks before handler is fired. Defaults to 2. max\_clicks Maximum number of clicks after which the click counter is reset. A value of 0 means use "min" also as "max". timeout The delay after which a sequence of clicks is considered finished, and the click counter is reset. A value of 0 means to use the system default 300ms. last\_click\_only Defaults to false. When true, only the handler for the last multi-click is called. handler The [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) to fire on this event. Fired for multi-clicks, as "filtered" by the `last_click_only`, `min`, and `max` parameters. void window\_long\_click\_subscribe([ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) delay\_ms, [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) down\_handler, [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) up\_handler) Subscribe to long click events. ##### Notes Must be called from the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider). The back button cannot be overridden with a long click. #### Parameters button\_id The button events to subscribe to. delay\_ms Milliseconds after which "handler" is fired. A value of 0 means to use the system default 500ms. down\_handler The [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) to fire as soon as the button has been held for `delay_ms`. This may be NULL to have no down handler. up\_handler The [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) to fire on the release of a long click. This may be NULL to have no up handler. void window\_raw\_click\_subscribe([ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) down\_handler, [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) up\_handler, void \* context) Subscribe to raw click events. ##### Notes Must be called from within the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider). The back button cannot be overridden with a raw click. #### Parameters button\_id The button events to subscribe to. down\_handler The [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) to fire as soon as the button has been pressed. This may be NULL to have no down handler. up\_handler The [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) to fire on the release of the button. This may be NULL to have no up handler. context If this context is not NULL, it will override the general context. void window\_set\_click\_context([ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) button\_id, void \* context) Set the context that will be passed to handlers for the given button's events. By default the context passed to handlers is equal to the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) context (defaults to the window). ##### Note Must be called from within the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider). #### Parameters button\_id The button to set the context for. context Set the context that will be passed to handlers for the given button's events. void window\_attach\_recognizer(Window \* window, Recognizer \* recognizer) Attach a recognizer to the window. #### Parameters window [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md) to which to attach the [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) recognizer [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) to attach void window\_detach\_recognizer(Window \* window, Recognizer \* recognizer) Detach a recognizer from the window. #### Parameters window [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md) from which to detach the [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) recognizer [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) to detach void window\_set\_touch\_bridge\_disabled(Window \* window, bool disabled) Disable the system touch-navigation bridge for a window. With the bridge disabled the system recognizer set fails on Touchdown for this window, handing every touch to the recognizers the app attached, so an app can take over touch input instead of the built-in button emulation. #### Parameters window [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md) to configure disabled true to disable the bridge for this window ## Data Structure Documentation struct WindowHandlers [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) These handlers are called by the [Window Stack](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md) as windows get pushed on / popped. All these handlers use [WindowHandler](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler) as their function signature. #### Data Fields [WindowHandler](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler) load Called when the window is pushed to the screen when it's not loaded. This is a good moment to do the layout of the window. [WindowHandler](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler) appear Called when the window comes on the screen (again). E.g. when second-top-most window gets revealed (again) after popping the top-most window, but also when the window is pushed for the first time. This is a good moment to start timers related to the window, or reset the UI, etc. [WindowHandler](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler) disappear Called when the window leaves the screen, e.g. when another window is pushed, or this window is popped. Good moment to stop timers related to the window. [WindowHandler](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler) unload Called when the window is deinited, but could be used in the future to free resources bound to windows that are not on screen. #### See Also [window\_set\_window\_handlers()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_window_handlers) [Window Stack](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md) ## Typedef Documentation typedef struct Window Window typedef void(\* WindowHandler)(struct Window \*window) Function signature for a handler that deals with transition events of a window. #### See Also [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) [window\_set\_window\_handlers()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_window_handlers) --- # Window Stack Source: https://developer.repebble.com/docs/c/User_Interface/Window_Stack/ The multiple window manager In Pebble OS, the window stack serves as the global manager of what window is presented, ensuring that input events are forwarded to the topmost window. The navigation model of Pebble centers on the concept of a vertical “stack” of windows, similar to mobile app interactions. In working with the Window Stack API, the basic operations include push and pop. When an app wants to display a new window, it pushes a new window onto the stack. This appears like a window sliding in from the right. As an app is closed, the window is popped off the stack and disappears. For more complicated operations, involving multiple windows, you can determine which windows reside on the stack, using [window\_stack\_contains\_window()](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_contains_window) and remove any specific window, using [window\_stack\_remove()](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_remove). Refer to the [User Interface Layers chapter in the Pebble Developer Guides](https://developer.repebble.com/guides/user-interfaces/layers.md) (chapter "Window Stack") for a conceptual overview of the window stack and relevant code examples. Also see the [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) of a [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md) for the callbacks that can be added to a window in order to act upon window stack transitions. ## Function Documentation void window\_stack\_push(Window \* window, bool animated) Pushes the given window on the window navigation stack, on top of the current topmost window of the app. #### Parameters window The window to push on top animated Pass in `true` to animate the push using a sliding animation, or `false` to skip the animation. Window \* window\_stack\_pop(bool animated) Pops the topmost window on the navigation stack. #### Parameters animated See [window\_stack\_remove()](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_remove) #### Returns The window that is popped, or NULL if there are no windows to pop. void window\_stack\_pop\_all(const bool animated) Pops all windows. See [window\_stack\_remove()](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_remove) for a description of the `animated` parameter and notes. bool window\_stack\_remove(Window \* window, bool animated) Removes a given window from the window stack that belongs to the app task. ##### Note If there are no windows for the app left on the stack, the app will be killed by the system, shortly. To avoid this, make sure to push another window shortly after or before removing the last window. #### Parameters window The window to remove. If the window is NULL or if it is not on the stack, this function is a no-op. animated Pass in `true` to animate the removal of the window using a side-to-side sliding animation to reveal the next window. This is only used in case the window happens to be on top of the window stack (thus visible). #### Returns True if window was successfully removed, false otherwise. Window \* window\_stack\_get\_top\_window(void) Gets the topmost window on the stack that belongs to the app. #### Returns The topmost window on the stack that belongs to the app or NULL if no app window could be found. bool window\_stack\_contains\_window(Window \* window) Checks if the window is on the window stack. #### Parameters window The window to look for on the window stack #### Returns true if the window is currently on the window stack. --- # Worker Source: https://developer.repebble.com/docs/c/Worker/ ## Function Documentation void worker\_event\_loop(void) The event loop for workers, to be used in worker's main(). Will block until the worker is ready to exit. #### See Also [App](https://developer.repebble.com/docs/c/Foundation/App.md) void worker\_launch\_app(void) Launch the foreground app for this worker. --- # com.getpebble.android.kit Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/ All Classes and InterfacesClassesEnum Classes Class Description [Constants](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md "class in com.getpebble.android.kit") Constant values used by PebbleKit-enabled android applications. [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit") The Enum PebbleAppType. [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit") The Enum PebbleDataType. [PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md "class in com.getpebble.android.kit") A helper class providing methods for interacting with third-party Pebble Smartwatch applications. [PebbleKit.FirmwareVersionInfo](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md "class in com.getpebble.android.kit")   [PebbleKit.PebbleAckReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.md "class in com.getpebble.android.kit") A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE\_ACK' intents broadcast from pebble .apk. [PebbleKit.PebbleDataLogReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md "class in com.getpebble.android.kit") A special-purpose BroadcastReceiver that makes it easy to handle 'DATA\_AVAILABLE' data logging intents broadcast from pebble.apk. [PebbleKit.PebbleDataReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.md "class in com.getpebble.android.kit") A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE' intents broadcast from pebble.apk. [PebbleKit.PebbleNackReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.md "class in com.getpebble.android.kit") A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE\_NACK' intents broadcast from pebble .apk. --- # com.getpebble.android.kit.util Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/ All Classes and InterfacesClassesExceptions Class Description [PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util") A collection of key-value pairs of heterogeneous types. [PebbleDictionary.PebbleDictTypeException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException.md "class in com.getpebble.android.kit.util")   [PebbleDictionary.TupleOverflowException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException.md "class in com.getpebble.android.kit.util")   [PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md "class in com.getpebble.android.kit.util") A key-value pair stored in a [`PebbleDictionary`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util"). [PebbleTuple.ValueOverflowException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.ValueOverflowException.md "class in com.getpebble.android.kit.util")   [SportsState](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md "class in com.getpebble.android.kit.util") This container class includes new values of time, distance, and pace or speed. --- # Constant Values Source: https://developer.repebble.com/docs/pebblekit-android/com/constant-values/ ## com.getpebble.\* - com.getpebble.android.kit.[Constants](https://developer.repebble.com/docs/com/getpebble/android/kit/Constants/ "class in com.getpebble.android.kit") Modifier and Type Constant Field Value `public static final String` `APP_UUID` `"uuid"` `public static final String` `CUST_APP_TYPE` `"app_type"` `public static final String` `CUST_ICON` `"icon"` `public static final String` `CUST_NAME` `"name"` `public static final String` `DATA_LOG_TAG` `"data_log_tag"` `public static final String` `DATA_LOG_TIMESTAMP` `"data_log_timestamp"` `public static final String` `DATA_LOG_UUID` `"data_log_uuid"` `public static final int` `GOLF_BACK_KEY` `2` `public static final int` `GOLF_CMD_KEY` `5` `public static final int` `GOLF_CMD_NEXT` `2` `public static final int` `GOLF_CMD_PREV` `1` `public static final int` `GOLF_FRONT_KEY` `0` `public static final int` `GOLF_HOLE_KEY` `3` `public static final int` `GOLF_MID_KEY` `1` `public static final int` `GOLF_PAR_KEY` `4` `public static final String` `INTENT_APP_ACK` `"com.getpebble.action.app.ACK"` `public static final String` `INTENT_APP_CUSTOMIZE` `"com.getpebble.action.app.CONFIGURE"` `public static final String` `INTENT_APP_NACK` `"com.getpebble.action.app.NACK"` `public static final String` `INTENT_APP_RECEIVE` `"com.getpebble.action.app.RECEIVE"` `public static final String` `INTENT_APP_RECEIVE_ACK` `"com.getpebble.action.app.RECEIVE_ACK"` `public static final String` `INTENT_APP_RECEIVE_NACK` `"com.getpebble.action.app.RECEIVE_NACK"` `public static final String` `INTENT_APP_SEND` `"com.getpebble.action.app.SEND"` `public static final String` `INTENT_APP_START` `"com.getpebble.action.app.START"` `public static final String` `INTENT_APP_STOP` `"com.getpebble.action.app.STOP"` `public static final String` `INTENT_DL_ACK_DATA` `"com.getpebble.action.dl.ACK_DATA"` `public static final String` `INTENT_DL_FINISH_SESSION` `"com.getpebble.action.dl.FINISH_SESSION_NEW"` `public static final String` `INTENT_DL_RECEIVE_DATA` `"com.getpebble.action.dl.RECEIVE_DATA_NEW"` `public static final String` `INTENT_DL_REQUEST_DATA` `"com.getpebble.action.dl.REQUEST_DATA"` `public static final String` `INTENT_PEBBLE_CONNECTED` `"com.getpebble.action.PEBBLE_CONNECTED"` `public static final String` `INTENT_PEBBLE_DISCONNECTED` `"com.getpebble.action.PEBBLE_DISCONNECTED"` `public static final int` `KIT_STATE_COLUMN_APPMSG_SUPPORT` `1` `public static final int` `KIT_STATE_COLUMN_CONNECTED` `0` `public static final int` `KIT_STATE_COLUMN_DATALOGGING_SUPPORT` `2` `public static final int` `KIT_STATE_COLUMN_VERSION_MAJOR` `3` `public static final int` `KIT_STATE_COLUMN_VERSION_MINOR` `4` `public static final int` `KIT_STATE_COLUMN_VERSION_POINT` `5` `public static final int` `KIT_STATE_COLUMN_VERSION_TAG` `6` `public static final String` `MSG_DATA` `"msg_data"` `public static final String` `PBL_DATA_ID` `"pbl_data_id"` `public static final String` `PBL_DATA_OBJECT` `"pbl_data_object"` `public static final String` `PBL_DATA_TYPE` `"pbl_data_type"` `public static final int` `SPORTS_CUSTOM_LABEL_KEY` `7` `public static final int` `SPORTS_CUSTOM_VALUE_KEY` `8` `public static final int` `SPORTS_DATA_KEY` `2` `public static final int` `SPORTS_DATA_PACE` `1` `public static final int` `SPORTS_DATA_SPEED` `0` `public static final int` `SPORTS_DISTANCE_KEY` `1` `public static final int` `SPORTS_HR_BPM_KEY` `6` `public static final int` `SPORTS_LABEL_KEY` `5` `public static final int` `SPORTS_STATE_END` `3` `public static final int` `SPORTS_STATE_INIT` `0` `public static final int` `SPORTS_STATE_KEY` `4` `public static final int` `SPORTS_STATE_PAUSED` `2` `public static final int` `SPORTS_STATE_RUNNING` `1` `public static final int` `SPORTS_TIME_KEY` `0` `public static final int` `SPORTS_UNITS_IMPERIAL` `0` `public static final int` `SPORTS_UNITS_KEY` `3` `public static final int` `SPORTS_UNITS_METRIC` `1` `public static final String` `TRANSACTION_ID` `"transaction_id"` --- # Constants Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") com.getpebble.android.kit.Constants * * * public final class Constantsextends [Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") Constant values used by PebbleKit-enabled android applications. - ## Nested Class Summary Nested Classes Modifier and Type Class Description `static enum ` `Constants.PebbleAppType` The Enum PebbleAppType. `static enum ` `Constants.PebbleDataType` The Enum PebbleDataType. - ## Field Summary Fields Modifier and Type Field Description `static final String` `APP_UUID` The bundle-key used to store a message's UUID. `static final String` `CUST_APP_TYPE` The bundle-key used to store the type of application being customized in a CUSTOMIZE intent. `static final String` `CUST_ICON` The bundle-key used to store the custom icon provided in a CUSTOMIZE intent. `static final String` `CUST_NAME` The bundle-key used to store the custom name provided in a CUSTOMIZE intent. `static final String` `DATA_LOG_TAG` A bundle-key used to store the tag for the corresponding data log. `static final String` `DATA_LOG_TIMESTAMP` The bundle-key used to store the timestamp of when a data log was first created. `static final String` `DATA_LOG_UUID` A bundle-key used to store the UUID that uniquely identifies a data log. `static final int` `GOLF_BACK_KEY` The Constant GOLF\_BACK\_KEY. `static final int` `GOLF_CMD_KEY` The Constant GOLF\_CMD\_KEY. `static final int` `GOLF_CMD_NEXT` Command sent by the golf-application to display the previous hole. `static final int` `GOLF_CMD_PREV` Command sent by the golf-application to display the next hole. `static final int` `GOLF_FRONT_KEY` The Constant GOLF\_FRONT\_KEY. `static final int` `GOLF_HOLE_KEY` The Constant GOLF\_HOLE\_KEY. `static final int` `GOLF_MID_KEY` The Constant GOLF\_MID\_KEY. `static final int` `GOLF_PAR_KEY` The Constant GOLF\_PAR\_KEY. `static final UUID` `GOLF_UUID` The UUID corresponding to Pebble's built-in "Golf" application. `static final String` `INTENT_APP_ACK` Intent broadcast to pebble.apk to indicate that a message was received from the watch. `static final String` `INTENT_APP_CUSTOMIZE` Intent broadcast to pebble.apk responsible for customizing the name and icon of the 'stock' Sports and Golf applications included in the watch's firmware. `static final String` `INTENT_APP_NACK` Intent broadcast to pebble.apk to indicate that a message was unsuccessfully received from the watch. `static final String` `INTENT_APP_RECEIVE` Intent broadcast from pebble.apk containing one-or-more key-value pairs sent from the watch to the phone. `static final String` `INTENT_APP_RECEIVE_ACK` Intent broadcast from pebble.apk indicating that a sent message was successfully received by a watch app. `static final String` `INTENT_APP_RECEIVE_NACK` Intent broadcast from pebble.apk indicating that a sent message was not received by a watch app. `static final String` `INTENT_APP_SEND` Intent broadcast to pebble.apk containing one-or-more key-value pairs to be sent to the watch from the phone. `static final String` `INTENT_APP_START` Intent broadcast to pebble.apk responsible for launching a watch-app on the connected watch. `static final String` `INTENT_APP_STOP` Intent broadcast to pebble.apk responsible for closing a running watch-app on the connected watch. `static final String` `INTENT_DL_ACK_DATA` Intent broadcast to pebble.apk implicitly when a unit of data from a data log is received. `static final String` `INTENT_DL_FINISH_SESSION` Intent broadcast from pebble.apk indicating the session has finished. `static final String` `INTENT_DL_RECEIVE_DATA` Intent broadcast from pebble.apk containing a unit of data from a data log. `static final String` `INTENT_DL_REQUEST_DATA` Intent broadcast to pebble.apk to request data logs for a particular app. `static final String` `INTENT_PEBBLE_CONNECTED` Intent broadcast by pebble.apk when a new connection to a Pebble is established. `static final String` `INTENT_PEBBLE_DISCONNECTED` Intent broadcast by pebble.apk when the connection to a Pebble is closed or lost. `static final int` `KIT_STATE_COLUMN_APPMSG_SUPPORT`   `static final int` `KIT_STATE_COLUMN_CONNECTED`   `static final int` `KIT_STATE_COLUMN_DATALOGGING_SUPPORT`   `static final int` `KIT_STATE_COLUMN_VERSION_MAJOR`   `static final int` `KIT_STATE_COLUMN_VERSION_MINOR`   `static final int` `KIT_STATE_COLUMN_VERSION_POINT`   `static final int` `KIT_STATE_COLUMN_VERSION_TAG`   `static final String` `MSG_DATA` The bundle-key used to store a message's JSON payload send-to or received-from the watch. `static final String` `PBL_DATA_ID` A bundle-key used to store the ID of a unit of data in a data log. `static final String` `PBL_DATA_OBJECT` A bundle-key used to store the value of the data unit. `static final String` `PBL_DATA_TYPE` A bundle-key used to store the data type of the data unit. `static final int` `SPORTS_CUSTOM_LABEL_KEY` The PebbleDictionary key corresponding to the custom label field sent to the Sports watch-app. `static final int` `SPORTS_CUSTOM_VALUE_KEY` The PebbleDictionary key corresponding to the custom value sent to the Sports watch-app. `static final int` `SPORTS_DATA_KEY` The PebbleDictionary key corresponding to the 'data' field sent to the Sports watch-app. `static final int` `SPORTS_DATA_PACE` PebbleDictionary value corresponding to 'pace' data. `static final int` `SPORTS_DATA_SPEED` PebbleDictionary value corresponding to 'speed' data. `static final int` `SPORTS_DISTANCE_KEY` The PebbleDictionary key corresponding to the 'distance' field sent to the Sports watch-app. `static final int` `SPORTS_HR_BPM_KEY` The PebbleDictionary key corresponding to the 'heart rate' field sent to the Sports watch-app. `static final int` `SPORTS_LABEL_KEY` The PebbleDictionary key corresponding to the 'label' field sent to the Sports watch-app. `static final int` `SPORTS_STATE_END` The Constant SPORTS\_STATE\_END. `static final int` `SPORTS_STATE_INIT` The Constant SPORTS\_STATE\_INIT. `static final int` `SPORTS_STATE_KEY` The PebbleDictionary key corresponding to the 'state' field sent to the Sports watch-app. `static final int` `SPORTS_STATE_PAUSED` The Constant SPORTS\_STATE\_PAUSED. `static final int` `SPORTS_STATE_RUNNING` The Constant SPORTS\_STATE\_RUNNING. `static final int` `SPORTS_TIME_KEY` The PebbleDictionary key corresponding to the 'time' field sent to the Sports watch-app. `static final int` `SPORTS_UNITS_IMPERIAL` PebbleDictionary value corresponding to 'imperial' units. `static final int` `SPORTS_UNITS_KEY` The PebbleDictionary key corresponding to the 'units' field sent to the Sports watch-app. `static final int` `SPORTS_UNITS_METRIC` PebbleDictionary value corresponding to 'metric' units. `static final UUID` `SPORTS_UUID` The UUID corresponding to Pebble's built-in "Sports" application. `static final String` `TRANSACTION_ID` The bundle-key used to store a message's transaction id. - ## Method Summary ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Field Details - ### INTENT\_PEBBLE\_CONNECTED public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_PEBBLE\_CONNECTED Intent broadcast by pebble.apk when a new connection to a Pebble is established. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_PEBBLE_CONNECTED) - ### INTENT\_PEBBLE\_DISCONNECTED public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_PEBBLE\_DISCONNECTED Intent broadcast by pebble.apk when the connection to a Pebble is closed or lost. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_PEBBLE_DISCONNECTED) - ### INTENT\_APP\_ACK public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_ACK Intent broadcast to pebble.apk to indicate that a message was received from the watch. To avoid protocol timeouts on the watch, applications _must_ ACK or NACK all received messages. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_ACK) - ### INTENT\_APP\_NACK public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_NACK Intent broadcast to pebble.apk to indicate that a message was unsuccessfully received from the watch. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_NACK) - ### INTENT\_APP\_RECEIVE public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_RECEIVE Intent broadcast from pebble.apk containing one-or-more key-value pairs sent from the watch to the phone. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_RECEIVE) - ### INTENT\_APP\_RECEIVE\_ACK public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_RECEIVE\_ACK Intent broadcast from pebble.apk indicating that a sent message was successfully received by a watch app. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_RECEIVE_ACK) - ### INTENT\_APP\_RECEIVE\_NACK public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_RECEIVE\_NACK Intent broadcast from pebble.apk indicating that a sent message was not received by a watch app. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_RECEIVE_NACK) - ### INTENT\_APP\_SEND public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_SEND Intent broadcast to pebble.apk containing one-or-more key-value pairs to be sent to the watch from the phone. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_SEND) - ### INTENT\_APP\_START public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_START Intent broadcast to pebble.apk responsible for launching a watch-app on the connected watch. This intent is idempotent. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_START) - ### INTENT\_APP\_STOP public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_STOP Intent broadcast to pebble.apk responsible for closing a running watch-app on the connected watch. This intent is idempotent. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_STOP) - ### INTENT\_APP\_CUSTOMIZE public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_APP\_CUSTOMIZE Intent broadcast to pebble.apk responsible for customizing the name and icon of the 'stock' Sports and Golf applications included in the watch's firmware. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_APP_CUSTOMIZE) - ### INTENT\_DL\_RECEIVE\_DATA public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_DL\_RECEIVE\_DATA Intent broadcast from pebble.apk containing a unit of data from a data log. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_DL_RECEIVE_DATA) - ### INTENT\_DL\_ACK\_DATA public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_DL\_ACK\_DATA Intent broadcast to pebble.apk implicitly when a unit of data from a data log is received. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_DL_ACK_DATA) - ### INTENT\_DL\_REQUEST\_DATA public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_DL\_REQUEST\_DATA Intent broadcast to pebble.apk to request data logs for a particular app. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_DL_REQUEST_DATA) - ### INTENT\_DL\_FINISH\_SESSION public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") INTENT\_DL\_FINISH\_SESSION Intent broadcast from pebble.apk indicating the session has finished. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.INTENT_DL_FINISH_SESSION) - ### SPORTS\_UUID public static final [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") SPORTS\_UUID The UUID corresponding to Pebble's built-in "Sports" application. - ### GOLF\_UUID public static final [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") GOLF\_UUID The UUID corresponding to Pebble's built-in "Golf" application. - ### TRANSACTION\_ID public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") TRANSACTION\_ID The bundle-key used to store a message's transaction id. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.TRANSACTION_ID) - ### APP\_UUID public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") APP\_UUID The bundle-key used to store a message's UUID. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.APP_UUID) - ### MSG\_DATA public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") MSG\_DATA The bundle-key used to store a message's JSON payload send-to or received-from the watch. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.MSG_DATA) - ### CUST\_APP\_TYPE public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") CUST\_APP\_TYPE The bundle-key used to store the type of application being customized in a CUSTOMIZE intent. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.CUST_APP_TYPE) - ### CUST\_NAME public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") CUST\_NAME The bundle-key used to store the custom name provided in a CUSTOMIZE intent. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.CUST_NAME) - ### CUST\_ICON public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") CUST\_ICON The bundle-key used to store the custom icon provided in a CUSTOMIZE intent. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.CUST_ICON) - ### DATA\_LOG\_TIMESTAMP public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") DATA\_LOG\_TIMESTAMP The bundle-key used to store the timestamp of when a data log was first created. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.DATA_LOG_TIMESTAMP) - ### DATA\_LOG\_UUID public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") DATA\_LOG\_UUID A bundle-key used to store the UUID that uniquely identifies a data log. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.DATA_LOG_UUID) - ### DATA\_LOG\_TAG public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") DATA\_LOG\_TAG A bundle-key used to store the tag for the corresponding data log. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.DATA_LOG_TAG) - ### PBL\_DATA\_ID public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") PBL\_DATA\_ID A bundle-key used to store the ID of a unit of data in a data log. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.PBL_DATA_ID) - ### PBL\_DATA\_TYPE public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") PBL\_DATA\_TYPE A bundle-key used to store the data type of the data unit. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.PBL_DATA_TYPE) - ### PBL\_DATA\_OBJECT public static final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") PBL\_DATA\_OBJECT A bundle-key used to store the value of the data unit. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.PBL_DATA_OBJECT) - ### SPORTS\_TIME\_KEY public static final int SPORTS\_TIME\_KEY The PebbleDictionary key corresponding to the 'time' field sent to the Sports watch-app. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_TIME_KEY) - ### SPORTS\_DISTANCE\_KEY public static final int SPORTS\_DISTANCE\_KEY The PebbleDictionary key corresponding to the 'distance' field sent to the Sports watch-app. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_DISTANCE_KEY) - ### SPORTS\_DATA\_KEY public static final int SPORTS\_DATA\_KEY The PebbleDictionary key corresponding to the 'data' field sent to the Sports watch-app. The data field is paired with the label specified by SPORTS\_LABEL\_KEY and can be used to display speed or pace data. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_DATA_KEY) - ### SPORTS\_UNITS\_KEY public static final int SPORTS\_UNITS\_KEY The PebbleDictionary key corresponding to the 'units' field sent to the Sports watch-app. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_UNITS_KEY) - ### SPORTS\_STATE\_KEY public static final int SPORTS\_STATE\_KEY The PebbleDictionary key corresponding to the 'state' field sent to the Sports watch-app. Both the watch and phone-app may modify this field. The phone-application is responsible for performing any required state transitions to stay in sync with the watch-app's state. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_STATE_KEY) - ### SPORTS\_LABEL\_KEY public static final int SPORTS\_LABEL\_KEY The PebbleDictionary key corresponding to the 'label' field sent to the Sports watch-app. The label field controls the label above the 'data' field. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_LABEL_KEY) - ### SPORTS\_HR\_BPM\_KEY public static final int SPORTS\_HR\_BPM\_KEY The PebbleDictionary key corresponding to the 'heart rate' field sent to the Sports watch-app. The heart rate field controls the data displayed in the scrollable field containing heart rate bpm. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_HR_BPM_KEY) - ### SPORTS\_CUSTOM\_LABEL\_KEY public static final int SPORTS\_CUSTOM\_LABEL\_KEY The PebbleDictionary key corresponding to the custom label field sent to the Sports watch-app. This label field controls the label above the custom value field. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_CUSTOM_LABEL_KEY) - ### SPORTS\_CUSTOM\_VALUE\_KEY public static final int SPORTS\_CUSTOM\_VALUE\_KEY The PebbleDictionary key corresponding to the custom value sent to the Sports watch-app. The custom value field controls the data displayed in the scrollable field containing custom data. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_CUSTOM_VALUE_KEY) - ### SPORTS\_UNITS\_IMPERIAL public static final int SPORTS\_UNITS\_IMPERIAL PebbleDictionary value corresponding to 'imperial' units. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_UNITS_IMPERIAL) - ### SPORTS\_UNITS\_METRIC public static final int SPORTS\_UNITS\_METRIC PebbleDictionary value corresponding to 'metric' units. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_UNITS_METRIC) - ### SPORTS\_DATA\_SPEED public static final int SPORTS\_DATA\_SPEED PebbleDictionary value corresponding to 'speed' data. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_DATA_SPEED) - ### SPORTS\_DATA\_PACE public static final int SPORTS\_DATA\_PACE PebbleDictionary value corresponding to 'pace' data. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_DATA_PACE) - ### SPORTS\_STATE\_INIT public static final int SPORTS\_STATE\_INIT The Constant SPORTS\_STATE\_INIT. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_STATE_INIT) - ### SPORTS\_STATE\_RUNNING public static final int SPORTS\_STATE\_RUNNING The Constant SPORTS\_STATE\_RUNNING. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_STATE_RUNNING) - ### SPORTS\_STATE\_PAUSED public static final int SPORTS\_STATE\_PAUSED The Constant SPORTS\_STATE\_PAUSED. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_STATE_PAUSED) - ### SPORTS\_STATE\_END public static final int SPORTS\_STATE\_END The Constant SPORTS\_STATE\_END. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.SPORTS_STATE_END) - ### GOLF\_FRONT\_KEY public static final int GOLF\_FRONT\_KEY The Constant GOLF\_FRONT\_KEY. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_FRONT_KEY) - ### GOLF\_MID\_KEY public static final int GOLF\_MID\_KEY The Constant GOLF\_MID\_KEY. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_MID_KEY) - ### GOLF\_BACK\_KEY public static final int GOLF\_BACK\_KEY The Constant GOLF\_BACK\_KEY. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_BACK_KEY) - ### GOLF\_HOLE\_KEY public static final int GOLF\_HOLE\_KEY The Constant GOLF\_HOLE\_KEY. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_HOLE_KEY) - ### GOLF\_PAR\_KEY public static final int GOLF\_PAR\_KEY The Constant GOLF\_PAR\_KEY. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_PAR_KEY) - ### GOLF\_CMD\_KEY public static final int GOLF\_CMD\_KEY The Constant GOLF\_CMD\_KEY. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_CMD_KEY) - ### GOLF\_CMD\_PREV public static final int GOLF\_CMD\_PREV Command sent by the golf-application to display the next hole. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_CMD_PREV) - ### GOLF\_CMD\_NEXT public static final int GOLF\_CMD\_NEXT Command sent by the golf-application to display the previous hole. See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.GOLF_CMD_NEXT) - ### KIT\_STATE\_COLUMN\_CONNECTED public static final int KIT\_STATE\_COLUMN\_CONNECTED See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.KIT_STATE_COLUMN_CONNECTED) - ### KIT\_STATE\_COLUMN\_APPMSG\_SUPPORT public static final int KIT\_STATE\_COLUMN\_APPMSG\_SUPPORT See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.KIT_STATE_COLUMN_APPMSG_SUPPORT) - ### KIT\_STATE\_COLUMN\_DATALOGGING\_SUPPORT public static final int KIT\_STATE\_COLUMN\_DATALOGGING\_SUPPORT See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.KIT_STATE_COLUMN_DATALOGGING_SUPPORT) - ### KIT\_STATE\_COLUMN\_VERSION\_MAJOR public static final int KIT\_STATE\_COLUMN\_VERSION\_MAJOR See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.KIT_STATE_COLUMN_VERSION_MAJOR) - ### KIT\_STATE\_COLUMN\_VERSION\_MINOR public static final int KIT\_STATE\_COLUMN\_VERSION\_MINOR See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.KIT_STATE_COLUMN_VERSION_MINOR) - ### KIT\_STATE\_COLUMN\_VERSION\_POINT public static final int KIT\_STATE\_COLUMN\_VERSION\_POINT See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.KIT_STATE_COLUMN_VERSION_POINT) - ### KIT\_STATE\_COLUMN\_VERSION\_TAG public static final int KIT\_STATE\_COLUMN\_VERSION\_TAG See Also: - [Constant Field Values](https://developer.repebble.com/docs/pebblekit-android/constant-values/#com.getpebble.android.kit.Constants.KIT_STATE_COLUMN_VERSION_TAG) --- # Constants.PebbleAppType Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") [java.lang.Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang")\<[Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit")\> com.getpebble.android.kit.Constants.PebbleAppType All Implemented Interfaces:`Serializable`, `Comparable`, `Constable`Enclosing class:[Constants](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md "class in com.getpebble.android.kit") * * * public static enum Constants.PebbleAppTypeextends [Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang")\<[Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit")\> The Enum PebbleAppType. - ## Nested Class Summary ## Nested classes/interfaces inherited from class java.lang.[Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang") `Enum.EnumDesc>` - ## Enum Constant Summary Enum Constants Enum Constant Description `GOLF` The golf. `OTHER` The other. `SPORTS` The sports. - ## Field Summary Fields Modifier and Type Field Description `final int` `ord` The ord. - ## Method Summary All MethodsStatic MethodsConcrete Methods Modifier and Type Method Description `static Constants.PebbleAppType` `valueOf(String name)` Returns the enum constant of this class with the specified name. `static Constants.PebbleAppType[]` `values()` Returns an array containing the constants of this enum class, in the order they are declared. ### Methods inherited from class java.lang.[Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang") `clone, compareTo, describeConstable, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `getClass, notify, notifyAll, wait, wait, wait` - ## Enum Constant Details - ### SPORTS public static final [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit") SPORTS The sports. - ### GOLF public static final [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit") GOLF The golf. - ### OTHER public static final [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit") OTHER The other. - ## Field Details - ### ord public final int ord The ord. - ## Method Details - ### values public static [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit")[] values() Returns an array containing the constants of this enum class, in the order they are declared. Returns:an array containing the constants of this enum class, in the order they are declared - ### valueOf public static [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit") valueOf([String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") name) Returns the enum constant of this class with the specified name. The string must match _exactly_ an identifier used to declare an enum constant in this class. (Extraneous whitespace characters are not permitted.) Parameters:`name` - the name of the enum constant to be returned.Returns:the enum constant with the specified nameThrows:`IllegalArgumentException` - if this enum class has no constant with the specified name`NullPointerException` - if the argument is null --- # Constants.PebbleDataType Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") [java.lang.Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang")\<[Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit")\> com.getpebble.android.kit.Constants.PebbleDataType All Implemented Interfaces:`Serializable`, `Comparable`, `Constable`Enclosing class:[Constants](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md "class in com.getpebble.android.kit") * * * public static enum Constants.PebbleDataTypeextends [Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang")\<[Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit")\> The Enum PebbleDataType. - ## Nested Class Summary ## Nested classes/interfaces inherited from class java.lang.[Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang") `Enum.EnumDesc>` - ## Enum Constant Summary Enum Constants Enum Constant Description `BYTES` The byte[]. `INT` The Integer. `INVALID` The Invalid. `UINT` The UnsignedInteger. - ## Field Summary Fields Modifier and Type Field Description `final byte` `ord` The ord. - ## Method Summary All MethodsStatic MethodsConcrete Methods Modifier and Type Method Description `static Constants.PebbleDataType` `fromByte(byte b)` Instantiates a new pebble data type from a byte. `static Constants.PebbleDataType` `valueOf(String name)` Returns the enum constant of this class with the specified name. `static Constants.PebbleDataType[]` `values()` Returns an array containing the constants of this enum class, in the order they are declared. ### Methods inherited from class java.lang.[Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html "class or interface in java.lang") `clone, compareTo, describeConstable, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `getClass, notify, notifyAll, wait, wait, wait` - ## Enum Constant Details - ### BYTES public static final [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit") BYTES The byte[]. - ### UINT public static final [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit") UINT The UnsignedInteger. - ### INT public static final [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit") INT The Integer. - ### INVALID public static final [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit") INVALID The Invalid. - ## Field Details - ### ord public final byte ord The ord. - ## Method Details - ### values public static [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit")[] values() Returns an array containing the constants of this enum class, in the order they are declared. Returns:an array containing the constants of this enum class, in the order they are declared - ### valueOf public static [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit") valueOf([String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") name) Returns the enum constant of this class with the specified name. The string must match _exactly_ an identifier used to declare an enum constant in this class. (Extraneous whitespace characters are not permitted.) Parameters:`name` - the name of the enum constant to be returned.Returns:the enum constant with the specified nameThrows:`IllegalArgumentException` - if this enum class has no constant with the specified name`NullPointerException` - if the argument is null - ### fromByte public static [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md "enum class in com.getpebble.android.kit") fromByte(byte b) Instantiates a new pebble data type from a byte. --- # PebbleDictionary Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") com.getpebble.android.kit.util.PebbleDictionary All Implemented Interfaces:`Iterable` * * * public class PebbleDictionaryextends [Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") implements [Iterable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Iterable.html "class or interface in java.lang")\<[PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md "class in com.getpebble.android.kit.util")\> A collection of key-value pairs of heterogeneous types. PebbleDictionaries are the primary structure used to exchange data between the phone and watch. To accommodate the mixed-types contained within a PebbleDictionary, an internal JSON representation is used when exchanging the dictionary between Android processes. - ## Nested Class Summary Nested Classes Modifier and Type Class Description `static class ` `PebbleDictionary.PebbleDictTypeException`   `static class ` `PebbleDictionary.TupleOverflowException`   - ## Field Summary Fields Modifier and Type Field Description `protected final Map` `tuples`   - ## Constructor Summary Constructors Constructor Description `PebbleDictionary()`   - ## Method Summary All MethodsStatic MethodsInstance MethodsConcrete Methods Modifier and Type Method Description `void` `addBytes(int key, byte[] bytes)` Associate the specified byte array with the provided key in the dictionary. `void` `addInt16(int key, short s)` Associate the specified signed short with the provided key in the dictionary. `void` `addInt32(int key, int i)` Associate the specified signed int with the provided key in the dictionary. `void` `addInt8(int key, byte b)` Associate the specified signed byte with the provided key in the dictionary. `void` `addString(int key, String value)` Associate the specified String with the provided key in the dictionary. `protected void` `addTuple(PebbleTuple tuple)`   `void` `addUint16(int key, short s)` Associate the specified unsigned short with the provided key in the dictionary. `void` `addUint32(int key, int i)` Associate the specified unsigned int with the provided key in the dictionary. `void` `addUint8(int key, byte b)` Associate the specified unsigned byte with the provided key in the dictionary. `boolean` `contains(int key)` Returns true if this dictionary contains a mapping for the specified key. `static PebbleDictionary` `fromJson(String jsonString)` Deserializes a JSON representation of a PebbleDictionary. `byte[]` `getBytes(int key)` Returns the byte array to which the specified key is mapped, or null if the key does not exist in this dictionary. `Long` `getInteger(int key)` Returns the signed integer to which the specified key is mapped, or null if the key does not exist in this dictionary. `String` `getString(int key)` Returns the string to which the specified key is mapped, or null if the key does not exist in this dictionary. `Long` `getUnsignedIntegerAsLong(int key)` Returns the unsigned integer as a long to which the specified key is mapped, or null if the key does not exist in this dictionary. `Iterator` `iterator()` `void` `remove(int key)` Removes the mapping for a key from this map if it is present. `int` `size()` Returns the number of key-value pairs in this dictionary. `String` `toJsonString()` Returns a JSON representation of this dictionary. ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` ### Methods inherited from interface java.lang.[Iterable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Iterable.html "class or interface in java.lang") `forEach, spliterator` - ## Field Details - ### tuples protected final [Map](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html "class or interface in java.util")\<[Integer](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Integer.html "class or interface in java.lang"),[PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md "class in com.getpebble.android.kit.util")\> tuples - ## Constructor Details - ### PebbleDictionary public PebbleDictionary() - ## Method Details - ### iterator public [Iterator](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Iterator.html "class or interface in java.util")\<[PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md "class in com.getpebble.android.kit.util")\> iterator() Specified by:`iterator` in interface `Iterable` - ### size public int size() Returns the number of key-value pairs in this dictionary. Returns:the number of key-value pairs in this dictionary - ### contains public boolean contains(int key) Returns true if this dictionary contains a mapping for the specified key. Parameters:`key` - key whose presence in this dictionary is to be testedReturns:true if this dictionary contains a mapping for the specified key - ### remove public void remove(int key) Removes the mapping for a key from this map if it is present. Parameters:`key` - key to be removed from the dictionary - ### addBytes public void addBytes(int key, byte[] bytes) Associate the specified byte array with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`bytes` - value to be associated with the specified key - ### addString public void addString(int key, [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") value) Associate the specified String with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`value` - value to be associated with the specified key - ### addInt8 public void addInt8(int key, byte b) Associate the specified signed byte with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`b` - value to be associated with the specified key - ### addUint8 public void addUint8(int key, byte b) Associate the specified unsigned byte with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`b` - value to be associated with the specified key - ### addInt16 public void addInt16(int key, short s) Associate the specified signed short with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`s` - value to be associated with the specified key - ### addUint16 public void addUint16(int key, short s) Associate the specified unsigned short with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`s` - value to be associated with the specified key - ### addInt32 public void addInt32(int key, int i) Associate the specified signed int with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`i` - value to be associated with the specified key - ### addUint32 public void addUint32(int key, int i) Associate the specified unsigned int with the provided key in the dictionary. If another key-value pair with the same key is already present in the dictionary, it will be replaced. Parameters:`key` - key with which the specified value is associated`i` - value to be associated with the specified key - ### getInteger public [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") getInteger(int key) Returns the signed integer to which the specified key is mapped, or null if the key does not exist in this dictionary. Parameters:`key` - key whose associated value is to be returnedReturns:value to which the specified key is mapped - ### getUnsignedIntegerAsLong public [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") getUnsignedIntegerAsLong(int key) Returns the unsigned integer as a long to which the specified key is mapped, or null if the key does not exist in this dictionary. We are using the Long type here so that we can remove the guava dependency. This is done so that we dont have incompatibility issues with the UnsignedInteger class from the Holo application, which uses a newer version of Guava. Parameters:`key` - key whose associated value is to be returnedReturns:value to which the specified key is mapped - ### getBytes public byte[] getBytes(int key) Returns the byte array to which the specified key is mapped, or null if the key does not exist in this dictionary. Parameters:`key` - key whose associated value is to be returnedReturns:value to which the specified key is mapped - ### getString public [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") getString(int key) Returns the string to which the specified key is mapped, or null if the key does not exist in this dictionary. Parameters:`key` - key whose associated value is to be returnedReturns:value to which the specified key is mapped - ### addTuple protected void addTuple([PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md "class in com.getpebble.android.kit.util") tuple) - ### toJsonString public [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") toJsonString() Returns a JSON representation of this dictionary. Returns:a JSON representation of this dictionary - ### fromJson public static [PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util") fromJson([String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") jsonString) throws org.json.JSONException Deserializes a JSON representation of a PebbleDictionary. Parameters:`jsonString` - the JSON representation to be deserializedThrows:`org.json.JSONException` - thrown if the specified JSON representation cannot be parsed --- # PebbleDictionary.PebbleDictTypeException Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") [java.lang.Throwable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Throwable.html "class or interface in java.lang") [java.lang.Exception](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Exception.html "class or interface in java.lang") [java.lang.RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") com.getpebble.android.kit.util.PebbleDictionary.PebbleDictTypeException All Implemented Interfaces:`Serializable`Enclosing class:[PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util") * * * public static class PebbleDictionary.PebbleDictTypeExceptionextends [RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") See Also: - [Serialized Form](https://developer.repebble.com/docs/pebblekit-android/serialized-form/#com.getpebble.android.kit.util.PebbleDictionary.PebbleDictTypeException) - ## Constructor Summary Constructors Constructor Description `PebbleDictTypeException(long key, com.getpebble.android.kit.util.PebbleTuple.TupleType expected, com.getpebble.android.kit.util.PebbleTuple.TupleType actual)`   - ## Method Summary ### Methods inherited from class java.lang.[Throwable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Throwable.html "class or interface in java.lang") `addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait` - ## Constructor Details - ### PebbleDictTypeException public PebbleDictTypeException(long key, com.getpebble.android.kit.util.PebbleTuple.TupleType expected, com.getpebble.android.kit.util.PebbleTuple.TupleType actual) --- # PebbleDictionary.TupleOverflowException Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") [java.lang.Throwable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Throwable.html "class or interface in java.lang") [java.lang.Exception](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Exception.html "class or interface in java.lang") [java.lang.RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") com.getpebble.android.kit.util.PebbleDictionary.TupleOverflowException All Implemented Interfaces:`Serializable`Enclosing class:[PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util") * * * public static class PebbleDictionary.TupleOverflowExceptionextends [RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") See Also: - [Serialized Form](https://developer.repebble.com/docs/pebblekit-android/serialized-form/#com.getpebble.android.kit.util.PebbleDictionary.TupleOverflowException) - ## Constructor Summary Constructors Constructor Description `TupleOverflowException()`   - ## Method Summary ### Methods inherited from class java.lang.[Throwable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Throwable.html "class or interface in java.lang") `addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait` - ## Constructor Details - ### TupleOverflowException public TupleOverflowException() --- # PebbleKit Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") com.getpebble.android.kit.PebbleKit * * * public final class PebbleKitextends [Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") A helper class providing methods for interacting with third-party Pebble Smartwatch applications. Pebble-enabled Android applications may use this class to assist in sending/receiving data between the watch and the phone. - ## Nested Class Summary Nested Classes Modifier and Type Class Description `static class ` `PebbleKit.FirmwareVersionInfo`   `static class ` `PebbleKit.PebbleAckReceiver` A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE\_ACK' intents broadcast from pebble .apk. `static class ` `PebbleKit.PebbleDataLogReceiver` A special-purpose BroadcastReceiver that makes it easy to handle 'DATA\_AVAILABLE' data logging intents broadcast from pebble.apk. `static class ` `PebbleKit.PebbleDataReceiver` A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE' intents broadcast from pebble.apk. `static class ` `PebbleKit.PebbleNackReceiver` A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE\_NACK' intents broadcast from pebble .apk. - ## Method Summary All MethodsStatic MethodsConcrete Methods Modifier and Type Method Description `static boolean` `areAppMessagesSupported(android.content.Context context)` Synchronously query the Pebble application to see if the connected watch is running a firmware version that supports PebbleKit messages. `static void` `closeAppOnPebble(android.content.Context context, UUID watchappUuid)` Send a message to the connected Pebble to close an application identified by a UUID. `static void` `customizeWatchApp(android.content.Context context, Constants.PebbleAppType appType, String name, android.graphics.Bitmap icon)` Send a message to the connected Pebble to "customize" a built-in PebbleKit watch-app. `static PebbleKit.FirmwareVersionInfo` `getWatchFWVersion(android.content.Context context)` Get the version information of the firmware running on a connected watch. `static boolean` `isDataLoggingSupported(android.content.Context context)` Synchronously query the Pebble application to see if the connected watch is running a firmware version that supports PebbleKit data logging. `static boolean` `isWatchConnected(android.content.Context context)` Synchronously query the Pebble application to see if an active Bluetooth connection to a watch currently exists. `static android.content.BroadcastReceiver` `registerDataLogReceiver(android.content.Context context, PebbleKit.PebbleDataLogReceiver receiver)` A convenience function to assist in programatically registering a broadcast receiver for the 'DATA\_AVAILABLE' intent. `static android.content.BroadcastReceiver` `registerPebbleConnectedReceiver(android.content.Context context, android.content.BroadcastReceiver receiver)` A convenience function to assist in programatically registering a broadcast receiver for the 'CONNECTED' intent. `static android.content.BroadcastReceiver` `registerPebbleDisconnectedReceiver(android.content.Context context, android.content.BroadcastReceiver receiver)` A convenience function to assist in programatically registering a broadcast receiver for the 'DISCONNECTED' intent. `static android.content.BroadcastReceiver` `registerReceivedAckHandler(android.content.Context context, PebbleKit.PebbleAckReceiver receiver)` A convenience function to assist in programatically registering a broadcast receiver for the 'RECEIVE\_ACK' intent. `static android.content.BroadcastReceiver` `registerReceivedDataHandler(android.content.Context context, PebbleKit.PebbleDataReceiver receiver)` A convenience function to assist in programatically registering a broadcast receiver for the 'RECEIVE' intent. `static android.content.BroadcastReceiver` `registerReceivedNackHandler(android.content.Context context, PebbleKit.PebbleNackReceiver receiver)` A convenience function to assist in programatically registering a broadcast receiver for the 'RECEIVE\_NACK' intent. `static void` `requestDataLogsForApp(android.content.Context context, UUID appUuid)` A convenience function to emit an intent to pebble.apk to request the data logs for a particular app. `static void` `sendAckToPebble(android.content.Context context, int transactionId)` Send a message to the connected watch acknowledging the receipt of a PebbleDictionary. `static void` `sendDataToPebble(android.content.Context context, UUID watchappUuid, PebbleDictionary data)` Send one-or-more key-value pairs to the watch-app identified by the provided UUID. `static void` `sendDataToPebbleWithTransactionId(android.content.Context context, UUID watchappUuid, PebbleDictionary data, int transactionId)` Send one-or-more key-value pairs to the watch-app identified by the provided UUID. `static void` `sendNackToPebble(android.content.Context context, int transactionId)` Send a message to the connected watch that the previously sent PebbleDictionary was not received successfully. `static void` `startAppOnPebble(android.content.Context context, UUID watchappUuid)` Send a message to the connected Pebble to launch an application identified by a UUID. ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Method Details - ### customizeWatchApp public static void customizeWatchApp(android.content.Context context, [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md "enum class in com.getpebble.android.kit") appType, [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") name, android.graphics.Bitmap icon) throws [IllegalArgumentException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/IllegalArgumentException.html "class or interface in java.lang") Send a message to the connected Pebble to "customize" a built-in PebbleKit watch-app. This is intended to allow third-party Android applications to apply custom branding (both name and icon) on the watch without needing to distribute a complete watch-app. Parameters:`context` - The context used to send the broadcast. (Protip: pass in the ApplicationContext here.)`appType` - The watch-app to be configured. Options are either`name` - The custom name to be applied to the watch-app. Names must be less than 32 characters in length.`icon` - The custom icon to be applied to the watch-app. Icons must be black-and-white bitmaps no larger than 32px in either dimension.Throws:`IllegalArgumentException` - Thrown if the specified name or icon are invalid. [`Constants.PebbleAppType.SPORTS`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md#SPORTS) or [`Constants.PebbleAppType.GOLF`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md#GOLF). - ### isWatchConnected public static boolean isWatchConnected(android.content.Context context) Synchronously query the Pebble application to see if an active Bluetooth connection to a watch currently exists. Parameters:`context` - The Android context used to perform the query. _Protip:_ You probably want to use your ApplicationContext here.Returns:true if an active connection to the watch currently exists, otherwise false. This method will also return false if the Pebble application is not installed on the user's handset. - ### areAppMessagesSupported public static boolean areAppMessagesSupported(android.content.Context context) Synchronously query the Pebble application to see if the connected watch is running a firmware version that supports PebbleKit messages. Parameters:`context` - The Android context used to perform the query. _Protip:_ You probably want to use your ApplicationContext here.Returns:true if the watch supports PebbleKit messages, otherwise false. This method will always return false if no Pebble is currently connected to the handset. - ### getWatchFWVersion public static [PebbleKit.FirmwareVersionInfo](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md "class in com.getpebble.android.kit") getWatchFWVersion(android.content.Context context) Get the version information of the firmware running on a connected watch. Parameters:`context` - The Android context used to perform the query. _Protip:_ You probably want to use your ApplicationContext here.Returns:null if the watch is disconnected or we can't get the version. Otherwise, a FirmwareVersionObject containing info on the watch FW version - ### isDataLoggingSupported public static boolean isDataLoggingSupported(android.content.Context context) Synchronously query the Pebble application to see if the connected watch is running a firmware version that supports PebbleKit data logging. Parameters:`context` - The Android context used to perform the query. _Protip:_ You probably want to use your ApplicationContext here.Returns:true if the watch supports PebbleKit messages, otherwise false. This method will always return false if no Pebble is currently connected to the handset. - ### startAppOnPebble public static void startAppOnPebble(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") watchappUuid) throws [IllegalArgumentException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/IllegalArgumentException.html "class or interface in java.lang") Send a message to the connected Pebble to launch an application identified by a UUID. If another application is currently running it will be terminated and the new application will be brought to the foreground. Parameters:`context` - The context used to send the broadcast.`watchappUuid` - A UUID uniquely identifying the target application. UUIDs for the stock PebbleKit applications are available in [`Constants`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md "class in com.getpebble.android.kit").Throws:`IllegalArgumentException` - Thrown if the specified UUID is invalid. - ### closeAppOnPebble public static void closeAppOnPebble(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") watchappUuid) throws [IllegalArgumentException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/IllegalArgumentException.html "class or interface in java.lang") Send a message to the connected Pebble to close an application identified by a UUID. If this application is not currently running, the message is ignored. Parameters:`context` - The context used to send the broadcast.`watchappUuid` - A UUID uniquely identifying the target application. UUIDs for the stock kit applications are available in [`Constants`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md "class in com.getpebble.android.kit").Throws:`IllegalArgumentException` - Thrown if the specified UUID is invalid. - ### sendDataToPebble public static void sendDataToPebble(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") watchappUuid, [PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util") data) throws [IllegalArgumentException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/IllegalArgumentException.html "class or interface in java.lang") Send one-or-more key-value pairs to the watch-app identified by the provided UUID. This is the primary method for sending data from the phone to a connected Pebble. The watch-app and phone-app must agree of the set and type of key-value pairs being exchanged. Type mismatches or missing keys will cause errors on the receiver's end. Parameters:`context` - The context used to send the broadcast.`watchappUuid` - A UUID uniquely identifying the target application. UUIDs for the stock kit applications are available in [`Constants`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md "class in com.getpebble.android.kit").`data` - A dictionary containing one-or-more key-value pairs. For more information about the types of data that can be stored, see [`PebbleDictionary`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util").Throws:`IllegalArgumentException` - Thrown in the specified PebbleDictionary or UUID is invalid. - ### sendDataToPebbleWithTransactionId public static void sendDataToPebbleWithTransactionId(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") watchappUuid, [PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util") data, int transactionId) throws [IllegalArgumentException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/IllegalArgumentException.html "class or interface in java.lang") Send one-or-more key-value pairs to the watch-app identified by the provided UUID. The watch-app and phone-app must agree of the set and type of key-value pairs being exchanged. Type mismatches or missing keys will cause errors on the receiver's end. Parameters:`context` - The context used to send the broadcast.`watchappUuid` - A UUID uniquely identifying the target application. UUIDs for the stock kit applications are available in [`Constants`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md "class in com.getpebble.android.kit").`data` - A dictionary containing one-or-more key-value pairs. For more information about the types of data that can be stored, see [`PebbleDictionary`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util").`transactionId` - An integer uniquely identifying the transaction. This can be used to correlate messages sent to the Pebble and ACK/NACKs received from the Pebble.Throws:`IllegalArgumentException` - Thrown in the specified PebbleDictionary or UUID is invalid. - ### sendAckToPebble public static void sendAckToPebble(android.content.Context context, int transactionId) throws [IllegalArgumentException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/IllegalArgumentException.html "class or interface in java.lang") Send a message to the connected watch acknowledging the receipt of a PebbleDictionary. To avoid protocol timeouts on the watch, applications _must_ ACK or NACK all received messages. Parameters:`context` - The context used to send the broadcast.`transactionId` - The transaction id of the message in which the data was received. Valid transaction IDs are between (0, 255).Throws:`IllegalArgumentException` - Thrown if an invalid transaction id is specified. - ### sendNackToPebble public static void sendNackToPebble(android.content.Context context, int transactionId) throws [IllegalArgumentException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/IllegalArgumentException.html "class or interface in java.lang") Send a message to the connected watch that the previously sent PebbleDictionary was not received successfully. To avoid protocol timeouts on the watch, applications _must_ ACK or NACK all received messages. Parameters:`context` - The context used to send the broadcast.`transactionId` - The transaction id of the message in which the data was received. Valid transaction IDs are between (0, 255).Throws:`IllegalArgumentException` - Thrown if an invalid transaction id is specified. - ### registerPebbleConnectedReceiver public static android.content.BroadcastReceiver registerPebbleConnectedReceiver(android.content.Context context, android.content.BroadcastReceiver receiver) A convenience function to assist in programatically registering a broadcast receiver for the 'CONNECTED' intent. To avoid leaking memory, activities registering BroadcastReceivers _must_ unregister them in the Activity's `Activity.onPause()` method. Parameters:`context` - The context in which to register the BroadcastReceiver.`receiver` - The receiver to be registered.Returns:The registered receiver.See Also: - [`Constants.INTENT_PEBBLE_CONNECTED`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_PEBBLE_CONNECTED) - ### registerPebbleDisconnectedReceiver public static android.content.BroadcastReceiver registerPebbleDisconnectedReceiver(android.content.Context context, android.content.BroadcastReceiver receiver) A convenience function to assist in programatically registering a broadcast receiver for the 'DISCONNECTED' intent. Go avoid leaking memory, activities registering BroadcastReceivers _must_ unregister them in the Activity's `Activity.onPause()` method. Parameters:`context` - The context in which to register the BroadcastReceiver.`receiver` - The receiver to be registered.Returns:The registered receiver.See Also: - [`Constants.INTENT_PEBBLE_DISCONNECTED`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_PEBBLE_DISCONNECTED) - ### registerReceivedDataHandler public static android.content.BroadcastReceiver registerReceivedDataHandler(android.content.Context context, [PebbleKit.PebbleDataReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.md "class in com.getpebble.android.kit") receiver) A convenience function to assist in programatically registering a broadcast receiver for the 'RECEIVE' intent. To avoid leaking memory, activities registering BroadcastReceivers _must_ unregister them in the Activity's `Activity.onPause()` method. Parameters:`context` - The context in which to register the BroadcastReceiver.`receiver` - The receiver to be registered.Returns:The registered receiver.See Also: - [`Constants.INTENT_APP_RECEIVE`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_APP_RECEIVE) - ### registerReceivedAckHandler public static android.content.BroadcastReceiver registerReceivedAckHandler(android.content.Context context, [PebbleKit.PebbleAckReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.md "class in com.getpebble.android.kit") receiver) A convenience function to assist in programatically registering a broadcast receiver for the 'RECEIVE\_ACK' intent. To avoid leaking memory, activities registering BroadcastReceivers _must_ unregister them in the Activity's `Activity.onPause()` method. Parameters:`context` - The context in which to register the BroadcastReceiver.`receiver` - The receiver to be registered.Returns:The registered receiver.See Also: - [`Constants.INTENT_APP_RECEIVE_ACK`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_APP_RECEIVE_ACK) - ### registerReceivedNackHandler public static android.content.BroadcastReceiver registerReceivedNackHandler(android.content.Context context, [PebbleKit.PebbleNackReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.md "class in com.getpebble.android.kit") receiver) A convenience function to assist in programatically registering a broadcast receiver for the 'RECEIVE\_NACK' intent. To avoid leaking memory, activities registering BroadcastReceivers _must_ unregister them in the Activity's `Activity.onPause()` method. Parameters:`context` - The context in which to register the BroadcastReceiver.`receiver` - The receiver to be registered.Returns:The registered receiver.See Also: - [`Constants.INTENT_APP_RECEIVE_NACK`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_APP_RECEIVE_NACK) - ### registerDataLogReceiver public static android.content.BroadcastReceiver registerDataLogReceiver(android.content.Context context, [PebbleKit.PebbleDataLogReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md "class in com.getpebble.android.kit") receiver) A convenience function to assist in programatically registering a broadcast receiver for the 'DATA\_AVAILABLE' intent. To avoid leaking memory, activities registering BroadcastReceivers _must_ unregister them in the Activity's `Activity.onPause()` method. Parameters:`context` - The context in which to register the BroadcastReceiver.`receiver` - The receiver to be registered.Returns:The registered receiver.See Also: - [`Constants.INTENT_DL_RECEIVE_DATA`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_DL_RECEIVE_DATA) - ### requestDataLogsForApp public static void requestDataLogsForApp(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") appUuid) A convenience function to emit an intent to pebble.apk to request the data logs for a particular app. If data is available, pebble.apk will advertise the data via 'INTENT\_DL\_RECEIVE\_DATA' intents. To avoid leaking memory, activities registering BroadcastReceivers _must_ unregister them in the Activity's `Activity.onPause()` method. Parameters:`context` - The context in which to register the BroadcastReceiver.`appUuid` - The app for which to request data logs.See Also: - [`Constants.INTENT_DL_RECEIVE_DATA`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_DL_RECEIVE_DATA) - [`Constants.INTENT_DL_REQUEST_DATA`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md#INTENT_DL_REQUEST_DATA) --- # PebbleKit Android Documentation Source: https://developer.repebble.com/docs/pebblekit-android/ This is the contents page for the PebbleKit Android SDK documentation, which includes all information on the two main packages below: ### [com.getpebble.android.kit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit.md) ### [com.getpebble.android.kit.util](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util.md) This part of the SDK can be used to build companion apps for Android to enhance the features of the watchapp or to integrate Pebble into an existing mobile app experience. Get started using PebbleKit Android by working through the [_Android Tutorial_](https://developer.repebble.com/tutorials/android-tutorial/part1). Check out [_PebbleKit Android_](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) to learn more about using this SDK. You can also find the source code for PebbleKit Android [on GitHub](https://github.com/pebble/pebble-android-sdk). --- # PebbleKit.FirmwareVersionInfo Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") com.getpebble.android.kit.PebbleKit.FirmwareVersionInfo Enclosing class:[PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md "class in com.getpebble.android.kit") * * * public static class PebbleKit.FirmwareVersionInfoextends [Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") - ## Method Summary All MethodsInstance MethodsConcrete Methods Modifier and Type Method Description `final int` `getMajor()`   `final int` `getMinor()`   `final int` `getPoint()`   `final String` `getTag()`   ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Method Details - ### getMajor public final int getMajor() - ### getMinor public final int getMinor() - ### getPoint public final int getPoint() - ### getTag public final [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") getTag() --- # PebbleKit.PebbleAckReceiver Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") android.content.BroadcastReceiver com.getpebble.android.kit.PebbleKit.PebbleAckReceiver Enclosing class:[PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md "class in com.getpebble.android.kit") * * * public abstract static class PebbleKit.PebbleAckReceiverextends android.content.BroadcastReceiver A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE\_ACK' intents broadcast from pebble .apk. - ## Nested Class Summary ## Nested classes/interfaces inherited from class android.content.BroadcastReceiver `android.content.BroadcastReceiver.PendingResult` - ## Constructor Summary Constructors Modifier Constructor Description `protected ` `PebbleAckReceiver(UUID subscribedUuid)` Instantiates a new pebble ack receiver. - ## Method Summary All MethodsInstance MethodsAbstract MethodsConcrete Methods Modifier and Type Method Description `void` `onReceive(android.content.Context context, android.content.Intent intent)` `abstract void` `receiveAck(android.content.Context context, int transactionId)` Handle the ACK received from the connected watch. ### Methods inherited from class android.content.BroadcastReceiver `abortBroadcast, clearAbortBroadcast, getAbortBroadcast, getDebugUnregister, getResultCode, getResultData, getResultExtras, getSentFromPackage, getSentFromUid, goAsync, isInitialStickyBroadcast, isOrderedBroadcast, peekService, setDebugUnregister, setOrderedHint, setResult, setResultCode, setResultData, setResultExtras` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Constructor Details - ### PebbleAckReceiver protected PebbleAckReceiver([UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") subscribedUuid) Instantiates a new pebble ack receiver. Parameters:`subscribedUuid` - the subscribed uuid - ## Method Details - ### receiveAck public abstract void receiveAck(android.content.Context context, int transactionId) Handle the ACK received from the connected watch. Parameters:`context` - The BroadcastReceiver's context.`transactionId` - The transaction ID of the message for which the ACK was received. This indicates which message was successfully received. - ### onReceive public void onReceive(android.content.Context context, android.content.Intent intent) Specified by:`onReceive` in class `android.content.BroadcastReceiver` --- # PebbleKit.PebbleDataLogReceiver Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") android.content.BroadcastReceiver com.getpebble.android.kit.PebbleKit.PebbleDataLogReceiver Enclosing class:[PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md "class in com.getpebble.android.kit") * * * public abstract static class PebbleKit.PebbleDataLogReceiverextends android.content.BroadcastReceiver A special-purpose BroadcastReceiver that makes it easy to handle 'DATA\_AVAILABLE' data logging intents broadcast from pebble.apk. - ## Nested Class Summary ## Nested classes/interfaces inherited from class android.content.BroadcastReceiver `android.content.BroadcastReceiver.PendingResult` - ## Constructor Summary Constructors Modifier Constructor Description `protected ` `PebbleDataLogReceiver(UUID subscribedUuid)` Instantiates a new pebble nack receiver. - ## Method Summary All MethodsInstance MethodsConcrete Methods Modifier and Type Method Description `void` `onFinishSession(android.content.Context context, UUID logUuid, Long timestamp, Long tag)` Called when a session has been finished on the watch and all data has been transmitted by pebble.apk `void` `onReceive(android.content.Context context, android.content.Intent intent)` `void` `receiveData(android.content.Context context, UUID logUuid, Long timestamp, Long tag, byte[] data)` Handle a byte array data unit that was logged the watch and broadcast by pebble.apk. `void` `receiveData(android.content.Context context, UUID logUuid, Long timestamp, Long tag, int data)` Handle an int data unit that was logged the watch and broadcast by pebble.apk. `void` `receiveData(android.content.Context context, UUID logUuid, Long timestamp, Long tag, Long data)` Handle an UnsignedInteger data unit that was logged the watch and broadcast by pebble.apk. ### Methods inherited from class android.content.BroadcastReceiver `abortBroadcast, clearAbortBroadcast, getAbortBroadcast, getDebugUnregister, getResultCode, getResultData, getResultExtras, getSentFromPackage, getSentFromUid, goAsync, isInitialStickyBroadcast, isOrderedBroadcast, peekService, setDebugUnregister, setOrderedHint, setResult, setResultCode, setResultData, setResultExtras` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Constructor Details - ### PebbleDataLogReceiver protected PebbleDataLogReceiver([UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") subscribedUuid) Instantiates a new pebble nack receiver. Parameters:`subscribedUuid` - the subscribed uuid - ## Method Details - ### receiveData public void receiveData(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") logUuid, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") timestamp, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") tag, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") data) Handle an UnsignedInteger data unit that was logged the watch and broadcast by pebble.apk. Parameters:`context` - The BroadcastReceiver's context.`logUuid` - The UUID that uniquely identifies a data log.`timestamp` - The timestamp when a data log was first created.`tag` - The user-defined tag for the corresponding data log.`data` - The unit of data that was logged on the watch.Throws:`UnsupportedOperationException` - Thrown if data is received and this handler is not implemented. - ### receiveData public void receiveData(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") logUuid, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") timestamp, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") tag, byte[] data) Handle a byte array data unit that was logged the watch and broadcast by pebble.apk. Parameters:`context` - The BroadcastReceiver's context.`logUuid` - The UUID that uniquely identifies a data log.`timestamp` - The timestamp when a data log was first created.`tag` - The user-defined tag for the corresponding data log.`data` - The unit of data that was logged on the watch.Throws:`UnsupportedOperationException` - Thrown if data is received and this handler is not implemented. - ### receiveData public void receiveData(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") logUuid, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") timestamp, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") tag, int data) Handle an int data unit that was logged the watch and broadcast by pebble.apk. Parameters:`context` - The BroadcastReceiver's context.`logUuid` - The UUID that uniquely identifies a data log.`timestamp` - The timestamp when a data log was first created.`tag` - The user-defined tag for the corresponding data log.`data` - The unit of data that was logged on the watch.Throws:`UnsupportedOperationException` - Thrown if data is received and this handler is not implemented. - ### onFinishSession public void onFinishSession(android.content.Context context, [UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") logUuid, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") timestamp, [Long](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Long.html "class or interface in java.lang") tag) Called when a session has been finished on the watch and all data has been transmitted by pebble.apk Parameters:`context` - The BroadcastReceiver's context.`logUuid` - The UUID that uniquely identifies a data log.`timestamp` - The timestamp when a data log was first created.`tag` - The user-defined tag for the corresponding data log. - ### onReceive public void onReceive(android.content.Context context, android.content.Intent intent) Specified by:`onReceive` in class `android.content.BroadcastReceiver` --- # PebbleKit.PebbleDataReceiver Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") android.content.BroadcastReceiver com.getpebble.android.kit.PebbleKit.PebbleDataReceiver Enclosing class:[PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md "class in com.getpebble.android.kit") * * * public abstract static class PebbleKit.PebbleDataReceiverextends android.content.BroadcastReceiver A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE' intents broadcast from pebble.apk. - ## Nested Class Summary ## Nested classes/interfaces inherited from class android.content.BroadcastReceiver `android.content.BroadcastReceiver.PendingResult` - ## Constructor Summary Constructors Modifier Constructor Description `protected ` `PebbleDataReceiver(UUID subscribedUuid)` Instantiates a new pebble data receiver. - ## Method Summary All MethodsInstance MethodsAbstract MethodsConcrete Methods Modifier and Type Method Description `void` `onReceive(android.content.Context context, android.content.Intent intent)` `abstract void` `receiveData(android.content.Context context, int transactionId, PebbleDictionary data)` Perform some work on the data received from the connected watch. ### Methods inherited from class android.content.BroadcastReceiver `abortBroadcast, clearAbortBroadcast, getAbortBroadcast, getDebugUnregister, getResultCode, getResultData, getResultExtras, getSentFromPackage, getSentFromUid, goAsync, isInitialStickyBroadcast, isOrderedBroadcast, peekService, setDebugUnregister, setOrderedHint, setResult, setResultCode, setResultData, setResultExtras` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Constructor Details - ### PebbleDataReceiver protected PebbleDataReceiver([UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") subscribedUuid) Instantiates a new pebble data receiver. Parameters:`subscribedUuid` - the subscribed uuid - ## Method Details - ### receiveData public abstract void receiveData(android.content.Context context, int transactionId, [PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util") data) Perform some work on the data received from the connected watch. Parameters:`context` - The BroadcastReceiver's context.`transactionId` - The transaction ID of the message in which the data was received. This is required when ACK/NACKing the received message.`data` - A dictionary of one-or-more key-value pairs received from the connected watch. - ### onReceive public void onReceive(android.content.Context context, android.content.Intent intent) Specified by:`onReceive` in class `android.content.BroadcastReceiver` --- # PebbleKit.PebbleNackReceiver Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") android.content.BroadcastReceiver com.getpebble.android.kit.PebbleKit.PebbleNackReceiver Enclosing class:[PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md "class in com.getpebble.android.kit") * * * public abstract static class PebbleKit.PebbleNackReceiverextends android.content.BroadcastReceiver A special-purpose BroadcastReceiver that makes it easy to handle 'RECEIVE\_NACK' intents broadcast from pebble .apk. - ## Nested Class Summary ## Nested classes/interfaces inherited from class android.content.BroadcastReceiver `android.content.BroadcastReceiver.PendingResult` - ## Constructor Summary Constructors Modifier Constructor Description `protected ` `PebbleNackReceiver(UUID subscribedUuid)` Instantiates a new pebble nack receiver. - ## Method Summary All MethodsInstance MethodsAbstract MethodsConcrete Methods Modifier and Type Method Description `void` `onReceive(android.content.Context context, android.content.Intent intent)` `abstract void` `receiveNack(android.content.Context context, int transactionId)` Handle the NACK received from the connected watch. ### Methods inherited from class android.content.BroadcastReceiver `abortBroadcast, clearAbortBroadcast, getAbortBroadcast, getDebugUnregister, getResultCode, getResultData, getResultExtras, getSentFromPackage, getSentFromUid, goAsync, isInitialStickyBroadcast, isOrderedBroadcast, peekService, setDebugUnregister, setOrderedHint, setResult, setResultCode, setResultData, setResultExtras` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Constructor Details - ### PebbleNackReceiver protected PebbleNackReceiver([UUID](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html "class or interface in java.util") subscribedUuid) Instantiates a new pebble nack receiver. Parameters:`subscribedUuid` - the subscribed uuid - ## Method Details - ### receiveNack public abstract void receiveNack(android.content.Context context, int transactionId) Handle the NACK received from the connected watch. Parameters:`context` - The BroadcastReceiver's context.`transactionId` - The transaction ID of the message for which the NACK was received. This indicates which message was not received. - ### onReceive public void onReceive(android.content.Context context, android.content.Intent intent) Specified by:`onReceive` in class `android.content.BroadcastReceiver` --- # PebbleTuple Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") com.getpebble.android.kit.util.PebbleTuple * * * public class PebbleTupleextends [Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") A key-value pair stored in a [`PebbleDictionary`](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md "class in com.getpebble.android.kit.util"). - ## Nested Class Summary Nested Classes Modifier and Type Class Description `static class ` `PebbleTuple.ValueOverflowException`   - ## Field Summary Fields Modifier and Type Field Description `final int` `key` The integer key identifying the tuple. `final int` `length` The length of the tuple's value in bytes. `final com.getpebble.android.kit.util.PebbleTuple.TupleType` `type` The type of value contained in the tuple. `final Object` `value` The value being associated with the tuple's key. `final com.getpebble.android.kit.util.PebbleTuple.Width` `width` The 'width' of the tuple's value; This value will always be 'NONE' for non-integer types. - ## Method Summary ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Field Details - ### key public final int key The integer key identifying the tuple. - ### type public final com.getpebble.android.kit.util.PebbleTuple.TupleType type The type of value contained in the tuple. - ### width public final com.getpebble.android.kit.util.PebbleTuple.Width width The 'width' of the tuple's value; This value will always be 'NONE' for non-integer types. - ### length public final int length The length of the tuple's value in bytes. - ### value public final [Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") value The value being associated with the tuple's key. --- # PebbleTuple.ValueOverflowException Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.ValueOverflowException/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") [java.lang.Throwable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Throwable.html "class or interface in java.lang") [java.lang.Exception](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Exception.html "class or interface in java.lang") [java.lang.RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") com.getpebble.android.kit.util.PebbleTuple.ValueOverflowException All Implemented Interfaces:`Serializable`Enclosing class:[PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md "class in com.getpebble.android.kit.util") * * * public static class PebbleTuple.ValueOverflowExceptionextends [RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") See Also: - [Serialized Form](https://developer.repebble.com/docs/pebblekit-android/serialized-form/#com.getpebble.android.kit.util.PebbleTuple.ValueOverflowException) - ## Constructor Summary Constructors Constructor Description `ValueOverflowException()`   - ## Method Summary ### Methods inherited from class java.lang.[Throwable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Throwable.html "class or interface in java.lang") `addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString` ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait` - ## Constructor Details - ### ValueOverflowException public ValueOverflowException() --- # Serialized Form Source: https://developer.repebble.com/docs/pebblekit-android/com/serialized-form/ ## Package [com.getpebble.android.kit.util](https://developer.repebble.com/docs/com/getpebble/android/kit/util/package-summary/) - ### Exception [com.getpebble.android.kit.util.PebbleDictionary.PebbleDictTypeException](https://developer.repebble.com/docs/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException/ "class in com.getpebble.android.kit.util") class PebbleDictTypeException extends [RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") implements [Serializable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Serializable.html "class or interface in java.io") - ### Exception [com.getpebble.android.kit.util.PebbleDictionary.TupleOverflowException](https://developer.repebble.com/docs/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException/ "class in com.getpebble.android.kit.util") class TupleOverflowException extends [RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") implements [Serializable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Serializable.html "class or interface in java.io") - ### Exception [com.getpebble.android.kit.util.PebbleTuple.ValueOverflowException](https://developer.repebble.com/docs/com/getpebble/android/kit/util/PebbleTuple.ValueOverflowException/ "class in com.getpebble.android.kit.util") class ValueOverflowException extends [RuntimeException](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/RuntimeException.html "class or interface in java.lang") implements [Serializable](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/Serializable.html "class or interface in java.io") --- # SportsState Source: https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState/ [java.lang.Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") com.getpebble.android.kit.util.SportsState * * * public class SportsStateextends [Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") This container class includes new values of time, distance, and pace or speed. To send the values to the watch use `synchronize`. This class is mutable, so consumers can update the values and synchronize the same instance again. Modifying the values of this class will not send the values. The consumer needs to invoke `synchronize` every time the UI needs to be updated. - ## Constructor Summary Constructors Constructor Description `SportsState()`   - ## Method Summary All MethodsInstance MethodsConcrete Methods Modifier and Type Method Description `String` `getCustomLabel()` The custom label to show in the sports UI. `String` `getCustomValue()` The custom value to show in the sports UI. `float` `getDistance()` The current distance in kilometers or miles. `byte` `getHeartBPM()` The current heart rate in beats per minute. `int` `getPaceInSec()` The current pace in seconds per kilometer or seconds per mile. `float` `getSpeed()` The current speed in kilometers per hour or miles per hour. `int` `getTimeInSec()` The current time in seconds. `void` `setCustomLabel(String customLabel)` Set the custom label to show in the sports UI. `void` `setCustomValue(String customValue)` Set the custom value to show in the sports UI. `void` `setDistance(float distance)` Set the current distance in kilometers or miles. `void` `setHeartBPM(byte heartBPM)` Set the current heart rate in beats per minute. `void` `setPaceInSec(int paceInSec)` Set the current pace in seconds per kilometer or seconds per mile. `void` `setSpeed(float speed)` Set the current speed in kilometers per hour or miles per hour. `void` `setTimeInSec(int timeInSec)` Set the current time in seconds. `void` `synchronize(android.content.Context context)` Synchronizes the current state of the Sports App to the connected watch. ### Methods inherited from class java.lang.[Object](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html "class or interface in java.lang") `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait` - ## Constructor Details - ### SportsState public SportsState() - ## Method Details - ### getTimeInSec public int getTimeInSec() The current time in seconds. Returns:the current activity duration in seconds - ### setTimeInSec public void setTimeInSec(int timeInSec) Set the current time in seconds. The possible range is currently limited from 0 to 35999, inclusive (9h 59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Hours, minutes and seconds will be separated by colons. The hours value will only appear if the value is more than 1 hour. Parameters:`timeInSec` - The current time to set, in seconds. - ### getDistance public float getDistance() The current distance in kilometers or miles. Returns:the current distance - ### setDistance public void setDistance(float distance) Set the current distance in kilometers or miles. The possible range is currently limited from 0 to 99.9, inclusive. Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a decimal number in the UI. The decimal part will be rounded to one digit. The unit of distance is dependent on the current unit setting. Parameters:`distance` - The current distance to set, in kilometers or miles. - ### getPaceInSec public int getPaceInSec() The current pace in seconds per kilometer or seconds per mile. Returns:the current pace - ### setPaceInSec public void setPaceInSec(int paceInSec) Set the current pace in seconds per kilometer or seconds per mile. The possible range is currently limited from 0 to 3599, inclusive (59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Minutes and seconds will be separated by colons. Currently pace and speed cannot be presented at the same time. Setting speed will discard the value set through pace. Parameters:`paceInSec` - The pace to set, in seconds per kilometer or seconds per mile. - ### getSpeed public float getSpeed() The current speed in kilometers per hour or miles per hour. Returns:the current speed - ### setSpeed public void setSpeed(float speed) Set the current speed in kilometers per hour or miles per hour. The possible range is currently limited from 0 to 99.9, inclusive. Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a decimal number in the UI. The decimal part will be rounded to one digit. Currently pace and speed cannot be presented at the same time. Setting pace will discard the value set through speed. Parameters:`speed` - The current speed to set, in kilometers per hour or miles per hour. - ### getHeartBPM public byte getHeartBPM() The current heart rate in beats per minute. If the heart rate has never been set before, this property will return zero. Returns:the current heart rate - ### setHeartBPM public void setHeartBPM(byte heartBPM) Set the current heart rate in beats per minute. Currently there is no way to stop sending heart rate values if one heart rate value was sent. The last value will be shown in the UI. Parameters:`heartBPM` - The current heart rate to set, in beats per minute. - ### getCustomLabel public [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") getCustomLabel() The custom label to show in the sports UI. Returns:the custom label - ### setCustomLabel public void setCustomLabel([String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") customLabel) Set the custom label to show in the sports UI. The maximum number of characters is ~10, but this maximum is not enforced. The label will be sent in upper case to the watch. To be sent, both customLabel and customValue have to be set to non-null values. Parameters:`customLabel` - The custom label to set. - ### getCustomValue public [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") getCustomValue() The custom value to show in the sports UI. Returns:the custom value - ### setCustomValue public void setCustomValue([String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html "class or interface in java.lang") customValue) Set the custom value to show in the sports UI. The maximum number of characters is ~8, but the maximum is not enforced. To be sent, both customValue and customLabel have to be set to non-null values. Parameters:`customValue` - The custom value to set. - ### synchronize public void synchronize(android.content.Context context) Synchronizes the current state of the Sports App to the connected watch. The method tries to send the minimal set of changes since the last time the method was used, to try to minimize communication with the watch. Parameters:`context` - The context used to send the broadcast. --- # FirmwareMetadataPlatform Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/FirmwareMetadataPlatform/ | Declared in | PBFirmwareMetadata.h | ### FirmwareMetadataPlatform Values specifying the Pebble hardware platform variant. #### Definition `typedef NS_ENUM(uint8_t, FirmwareMetadataPlatform ) {    FirmwareMetadataPlatformUnknown = 0,    FirmwareMetadataPlatformPebbleOneEV1 = 1,    FirmwareMetadataPlatformPebbleOneEV2 = 2,    FirmwareMetadataPlatformPebbleOneEV2_3 = 3,    FirmwareMetadataPlatformPebbleOneEV2_4 = 4,    FirmwareMetadataPlatformPebbleOnePointFive = 5,    FirmwareMetadataPlatformPebbleTwoPointZero = 6,    FirmwareMetadataPlatformPebbleSnowyEVT2 = 7,    FirmwareMetadataPlatformPebbleSnowyDVT = 8,    FirmwareMetadataPlatformPebbleSpaldingEVT = 9,    FirmwareMetadataPlatformPebbleBobbySmilesPVT = 10,    FirmwareMetadataPlatformPebbleSpalding = 11,    FirmwareMetadataPlatformPebbleSilkEVT = 12,    FirmwareMetadataPlatformPebbleRobertEVT = 13,    FirmwareMetadataPlatformPebbleSilk = 14,    FirmwareMetadataPlatformPebbleOneBigboard = 0 xff,    FirmwareMetadataPlatformPebbleOneBigboard2 = 0 xfe,    FirmwareMetadataPlatformPebbleSnowyBigboard = 0 xfd,    FirmwareMetadataPlatformPebbleSnowyBigboard2 = 0 xfc,    FirmwareMetadataPlatformPebbleSpaldingBigboard = 0 xfb,    FirmwareMetadataPlatformPebbleSilkBigboard = 0 xfa,    FirmwareMetadataPlatformPebbleRobertBigboard = 0 xf9,    FirmwareMetadataPlatformPebbleSilkBigboard2 = 0 xf8,    FirmwareMetadataPlatformPebbleRobertBigboard2 = 0 xf7, };` #### Constants `FirmwareMetadataPlatformUnknown` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleOneEV1` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleOneEV2` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleOneEV2_3` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleOneEV2_4` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleOnePointFive` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleTwoPointZero` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSnowyEVT2` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSnowyDVT` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSpaldingEVT` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleBobbySmilesPVT` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSpalding` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSilkEVT` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleRobertEVT` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSilk` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleOneBigboard` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleOneBigboard2` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSnowyBigboard` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSnowyBigboard2` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSpaldingBigboard` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSilkBigboard` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleRobertBigboard` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleSilkBigboard2` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. `FirmwareMetadataPlatformPebbleRobertBigboard2` Values specifying the Pebble hardware platform variant. Declared In `PBFirmwareMetadata.h`. #### Declared In `PBFirmwareMetadata.h` --- # GolfAppCommand Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/GolfAppCommand/ | Declared in | PBWatch+Golf.h | | References | [-[PBWatch golfAppAddReceiveUpdateHandler:]](Classes/PBWatch/#//api/name/golfAppAddReceiveUpdateHandler:) | ### GolfAppCommand The command IDs that can be sent back by the Golf watch app. #### Definition `typedef NS_ENUM(uint8_t, GolfAppCommand ) {    GolfAppCommandPrevious = 0 x01,    GolfAppCommandNext = 0 x02,    GolfAppCommandSelect = 0 x03, };` #### Constants `GolfAppCommandPrevious` The command IDs that can be sent back by the Golf watch app. Declared In `PBWatch+Golf.h`. `GolfAppCommandNext` The command IDs that can be sent back by the Golf watch app. Declared In `PBWatch+Golf.h`. `GolfAppCommandSelect` The command IDs that can be sent back by the Golf watch app. Declared In `PBWatch+Golf.h`. #### See Also - `[PBWatch golfAppAddReceiveUpdateHandler:]` #### Declared In `PBWatch+Golf.h` --- # NSData(Pebble) Category Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Categories/NSData+Pebble/ | Declared in | NSDictionary+Pebble.h | ### – pb\_dictionaryFromPebbleDictionaryDataWithError: Interprets the receiver as Pebble dict data and deserializes it into an NSDictionary. `- (nullable NSDictionary *)pb_dictionaryFromPebbleDictionaryDataWithError:(NSError *__autoreleasing *)error` #### Parameters _error_Pointer to an NSError \* that will be set after the method has returned in case there was an error. Possible error codes: PBErrorCodeDictionaryInternalConsistency #### See Also - `[NSDictionary(Pebble) pb_pebbleDictionaryData:]` #### Declared In `NSDictionary+Pebble.h` --- # NSDictionary(Pebble) Category Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Categories/NSDictionary+Pebble/ | Declared in | NSDictionary+Pebble.h | ### – pb\_pebbleDictionaryData: Serializes the receiver into a Pebble dict. `- (nullable NSData *)pb_pebbleDictionaryData:(NSError *__autoreleasing *)error` #### Parameters _error_Pointer to an NSError \* that will be set in case there was an error creating the dictionary. Possible error codes: PBErrorCodeDictionaryUnsupportedKeyClass PBErrorCodeDictionaryUnsupportedValueClass PBErrorCodeDictionaryUnsupportedValueClass #### Discussion The receiver dictionary must comply to the following constraints: - The keys must be instances of `NSNumber`. Their `-unsignedLongValue` return value will be used as final key. - The values must be instances of either `NSString`, `NSData` or `NSNumber`. Use the `NSNumber (stdint)` category to specify the signedness and width of `NSNumber`. #### See Also - `[NSData(Pebble) pb_dictionaryFromPebbleDictionaryDataWithError:]` #### Declared In `NSDictionary+Pebble.h` --- # NSError(Pebble) Category Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Categories/NSError+Pebble/ | Declared in | PBErrors.h | ### + pebbleErrorWithCode:underLyingError: Convenience method to create an NSError object with error domain and a given [PBErrorCode](../../Constants/PBErrorCode/) and with an underlying NSError object. `+ (NSError *)pebbleErrorWithCode:(PBErrorCode)code underLyingError:(NSError *__nullable)error` #### Parameters _code_The error code for which to create the error_error_The underlying error #### Declared In `PBErrors.h` ### + pebbleErrorWithCode: Convenience method to create an NSError object with error domain and a given [PBErrorCode](../../Constants/PBErrorCode/). `+ (NSError *)pebbleErrorWithCode:(PBErrorCode)code` #### Parameters _code_The error code for which to create the error #### Declared In `PBErrors.h` --- # NSNumber(PBStandardIntegerExtensions) Category Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber+PBStandardIntegerExtensions/ | Declared in | NSNumber+stdint.h | ## Overview This category exposes the capabilities the underlying CFNumber to explicitely store the signedness and the width of the storage. ### pb\_uint32Value Interprets the receiver as a 32-bits wide, unsigned integer. `@property (readonly) uint32_t pb_uint32Value` #### Declared In `NSNumber+stdint.h` ### pb\_uint16Value Interprets the receiver as a 16-bits wide, unsigned integer. `@property (readonly) uint16_t pb_uint16Value` #### Declared In `NSNumber+stdint.h` ### pb\_uint8Value Interprets the receiver as a 8-bits wide, unsigned integer. `@property (readonly) uint8_t pb_uint8Value` #### Declared In `NSNumber+stdint.h` ### pb\_int32Value Interprets the receiver as a 32-bits wide, signed integer. `@property (readonly) int32_t pb_int32Value` #### Declared In `NSNumber+stdint.h` ### pb\_int16Value Interprets the receiver as a 16-bits wide, signed integer. `@property (readonly) int16_t pb_int16Value` #### Declared In `NSNumber+stdint.h` ### pb\_int8Value Interprets the receiver as a 8-bits wide, signed integer. `@property (readonly) int8_t pb_int8Value` #### Declared In `NSNumber+stdint.h` ### pb\_float Gets whether the number that is stored by the receiver should be interpreted as a floating pointer number or not. `@property (readonly, getter=pb_isFloat) BOOL pb_float` #### Declared In `NSNumber+stdint.h` ### pb\_signed Gets whether the number that is stored by the receiver should be interpreted as a signed integer or not. `@property (readonly, getter=pb_isSigned) BOOL pb_signed` #### Declared In `NSNumber+stdint.h` ### pb\_byteWidth Gets the width in bytes of the integer that is stored by the receiver. `@property (readonly) uint8_t pb_byteWidth` #### Declared In `NSNumber+stdint.h` ### + pb\_numberWithUint32: Creates an NSNumber with a 32-bits wide, unsigned integer. `+ (NSNumber *)pb_numberWithUint32:(uint32_t)value` #### Parameters _value_The value for the created number. #### Declared In `NSNumber+stdint.h` ### + pb\_numberWithUint16: Creates an NSNumber with a 16-bits wide, unsigned integer. `+ (NSNumber *)pb_numberWithUint16:(uint16_t)value` #### Parameters _value_The value for the created number. #### Declared In `NSNumber+stdint.h` ### + pb\_numberWithUint8: Creates an NSNumber with a 8-bits wide, unsigned integer. `+ (NSNumber *)pb_numberWithUint8:(uint8_t)value` #### Parameters _value_The value for the created number. #### Declared In `NSNumber+stdint.h` ### + pb\_numberWithInt32: Creates an NSNumber with a 32-bits wide, signed integer. `+ (NSNumber *)pb_numberWithInt32:(int32_t)value` #### Parameters _value_The value for the created number. #### Declared In `NSNumber+stdint.h` ### + pb\_numberWithInt16: Creates an NSNumber with a 16-bits wide, signed integer. `+ (NSNumber *)pb_numberWithInt16:(int16_t)value` #### Parameters _value_The value for the created number. #### Declared In `NSNumber+stdint.h` ### + pb\_numberWithInt8: Creates an NSNumber with a 8-bits wide, signed integer. `+ (NSNumber *)pb_numberWithInt8:(int8_t)value` #### Parameters _value_The value for the created number. #### Declared In `NSNumber+stdint.h` --- # PBBitmap Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap/ | Inherits from | NSObject | | Declared in | PBBitmap.h | ## Overview Helper class to convert a UIImage to Pebble bitmap pixel data and GBitmap metadata. Refer to the documentation of Pebble’s native watch app SDK on the GBitmap format. ### pixelData The pixel data of the bitmap. This corresponds to the data as pointed to by GBitmap’s `addr` field. `@property (nonatomic, readonly, strong) NSData *pixelData` #### Declared In `PBBitmap.h` ### rowSizeBytes The number of bytes per row. Always a multiple of 4 bytes. `@property (nonatomic, readonly, assign) uint16_t rowSizeBytes` #### Declared In `PBBitmap.h` ### infoFlags Bitfield of metadata flags. This corresponds to GBitmap’s `info_flags` field. `@property (nonatomic, readonly, assign) uint16_t infoFlags` #### Declared In `PBBitmap.h` ### bounds The box of bits inside [`pixelData`](#//api/name/pixelData), that contains the actual image data to use. This corresponds to GBitmap’s `bounds` field. `@property (nonatomic, readonly, assign) GRect bounds` #### Declared In `PBBitmap.h` ### + pebbleBitmapWithUIImage: Factory method to create a PBBitmap from a UIImage instance. `+ (PBBitmap *)pebbleBitmapWithUIImage:(UIImage *)image` #### Parameters _image_The UIImage from which to create the PBBitmap #### Declared In `PBBitmap.h` --- # PBDataLoggingService Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService/ | Inherits from | NSObject | | Declared in | PBDataLoggingService.h | ## Overview The data logging service. Assign a [delegate](#//api/name/delegate) object in order to receive data from your watchapp. ### delegate The delegate that has the responsility of handling callbacks from the data logging service. `@property (atomic, readwrite, weak) id delegate` #### Declared In `PBDataLoggingService.h` ### – setDelegateQueue: Sets the queue on which [delegate](#//api/name/delegate) methods will be executed. Callbacks are intended to be processed in the order as they come in, so it you must use a serial queue. If set to nil (default) the main queue is used. `- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue` #### Parameters _delegateQueue_The queue on which the delegate methods will be executed. #### Declared In `PBDataLoggingService.h` ### – pollForData Query the latestConnectedWatch for data logging data. ( **Deprecated:** Use pollForDataFromWatch:) `- (void)pollForData` #### Declared In `PBDataLoggingService.h` ### – pollForDataFromWatch: Query the watch for data logging data. `- (void)pollForDataFromWatch:(PBWatch *)watch` #### Parameters _watch_The watch to query data from. #### Declared In `PBDataLoggingService.h` --- # PBDataLoggingServiceDelegate Protocol Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate/ | Conforms to | NSObject | | Declared in | PBDataLoggingService.h | ## Overview Data logging delegate protocol. The object that implements this protocol is responsible for handling incoming data. ### – dataLoggingService:hasByteArrays:numberOfItems:forDataLoggingSession: Called by the service every time there is data available that has not been consumed yet. `- (BOOL)dataLoggingService:(PBDataLoggingService *)service hasByteArrays:(const uint8_t *const)bytes numberOfItems:(uint16_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._bytes_Pointer to the array of bytes. The array contains (numberOfItems \* session.itemSize) bytes._numberOfItems_The number of items that the array contains._session_The metadata of the session. #### Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. #### Declared In `PBDataLoggingService.h` ### – dataLoggingService:hasUInt8s:numberOfItems:forDataLoggingSession: Called by the service every time there is data available that has not been consumed yet. `- (BOOL)dataLoggingService:(PBDataLoggingService *)service hasUInt8s:(const uint8_t [])data numberOfItems:(uint16_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._data_Pointer to the array of UInt8`s._numberOfItems_The number of items that the array contains._session_The metadata of the session. #### Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. #### Declared In `PBDataLoggingService.h` ### – dataLoggingService:hasUInt16s:numberOfItems:forDataLoggingSession: Called by the service every time there is data available that has not been consumed yet. `- (BOOL)dataLoggingService:(PBDataLoggingService *)service hasUInt16s:(const uint16_t [])data numberOfItems:(uint16_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._data_Pointer to the array of UInt16`s._numberOfItems_The number of items that the array contains._session_The metadata of the session. #### Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. #### Declared In `PBDataLoggingService.h` ### – dataLoggingService:hasUInt32s:numberOfItems:forDataLoggingSession: Called by the service every time there is data available that has not been consumed yet. `- (BOOL)dataLoggingService:(PBDataLoggingService *)service hasUInt32s:(const uint32_t [])data numberOfItems:(uint16_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._data_Pointer to the array of UInt32`s._numberOfItems_The number of items that the array contains._session_The metadata of the session. #### Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. #### Declared In `PBDataLoggingService.h` ### – dataLoggingService:hasSInt8s:numberOfItems:forDataLoggingSession: Called by the service every time there is data available that has not been consumed yet. `- (BOOL)dataLoggingService:(PBDataLoggingService *)service hasSInt8s:(const int8_t [])data numberOfItems:(uint16_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._data_Pointer to the array of SInt8`s._numberOfItems_The number of items that the array contains._session_The metadata of the session. #### Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. #### Declared In `PBDataLoggingService.h` ### – dataLoggingService:hasSInt16s:numberOfItems:forDataLoggingSession: Called by the service every time there is data available that has not been consumed yet. `- (BOOL)dataLoggingService:(PBDataLoggingService *)service hasSInt16s:(const int16_t [])data numberOfItems:(uint16_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._data_Pointer to the array of SInt16`s._numberOfItems_The number of items that the array contains._session_The metadata of the session. #### Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. #### Declared In `PBDataLoggingService.h` ### – dataLoggingService:hasSInt32s:numberOfItems:forDataLoggingSession: Called by the service every time there is data available that has not been consumed yet. `- (BOOL)dataLoggingService:(PBDataLoggingService *)service hasSInt32s:(const int32_t [])data numberOfItems:(uint16_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._data_Pointer to the array of SInt32`s._numberOfItems_The number of items that the array contains._session_The metadata of the session. #### Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. #### Declared In `PBDataLoggingService.h` ### – dataLoggingService:sessionDidFinish: Called by the service every time a session is finished. This is guaranteed to be called only after all data has been received and consumed by the application. `- (void)dataLoggingService:(PBDataLoggingService *)service sessionDidFinish:(PBDataLoggingSessionMetadata *)session` #### Parameters _service_The data logging service._session_The metadata of the session. #### Declared In `PBDataLoggingService.h` --- # PBDataLoggingSessionMetadata Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata/ | Inherits from | NSObject | | Conforms to | NSCopying NSSecureCoding | | Declared in | PBDataLoggingService.h | ## Overview The metadata of a data logging session. Used to identify a session. ### tag A tag associated with the session. `@property (nonatomic, readonly) uint32_t tag` #### Declared In `PBDataLoggingService.h` ### timestamp The timestamp of the moment the session was created by the watchapp. `@property (nonatomic, readonly) uint32_t timestamp` #### Declared In `PBDataLoggingService.h` ### type The type of data stored in this session. `@property (nonatomic, readonly) PBDataLoggingType type` #### Declared In `PBDataLoggingService.h` ### itemSize The size of a data item. `@property (nonatomic, readonly) uint16_t itemSize` #### Declared In `PBDataLoggingService.h` ### serialNumber The serial number of the watch that created the sessions. `@property (nonatomic, copy, readonly) NSString *serialNumber` #### Declared In `PBDataLoggingService.h` ### + metadataWithTag:timestamp:type:itemSize:serialNumber: Creates a new data logging session metadata object, given all its property values. This method is provided to create a metadata object that can be used to compare it with a metadata object that is passed into one of the [PBDataLoggingServiceDelegate](../../Protocols/PBDataLoggingServiceDelegate/) methods. `+ (instancetype)metadataWithTag:(uint32_t)tag timestamp:(uint32_t)timestamp type:(PBDataLoggingType)type itemSize:(uint16_t)itemSize serialNumber:(NSString *)serialNumber` #### Parameters _tag_The tag associated with the session_timestamp_The timestamp of the creation of the session_type_The type of data stored in the session_itemSize_The size of a data item_serialNumber_The serial number of the watch that created the session #### Return Value A session metadata object with the specified information #### Declared In `PBDataLoggingService.h` ### – isEqual: Tests the equality of the recipient and the given object. `- (BOOL)isEqual:(nullable id)object` #### Parameters _object_The object to check against. #### Return Value `YES` if all the property values of the receiver are equal to the property values of the other object. #### Discussion This method can be used to compare two sessions’ metadata objects to check whether they are referring to the same session or not. #### Declared In `PBDataLoggingService.h` ### – isEqualToDataLoggingSessionMetadata: Tests the equality of two data logging sessions’ metadata objects. `- (BOOL)isEqualToDataLoggingSessionMetadata:(PBDataLoggingSessionMetadata *)sessionMetadata` #### Parameters _sessionMetadata_The session metadata to compare against. #### Return Value `YES` if all the property values of the receiver are equal to the property values of the given session. #### Discussion This method can be used to compare two sessions’ metadata objects to check whether they are referring to the same session or not. **Note:** Equivalent to [[PBDataLoggingSessionMetadata isEqual:]](#//api/name/isEqual:) #### Declared In `PBDataLoggingService.h` --- # PBDataLoggingType Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/PBDataLoggingType/ | Declared in | PBDataLoggingService.h | ### PBDataLoggingType Data logging datatypes. #### Definition `typedef NS_ENUM(uint8_t, PBDataLoggingType ) {    PBDataLoggingTypeByteArray = 0,    PBDataLoggingTypeUInt = 2,    PBDataLoggingTypeInt = 3, };` #### Constants `PBDataLoggingTypeByteArray` Data logging datatypes. Declared In `PBDataLoggingService.h`. `PBDataLoggingTypeUInt` Data logging datatypes. Declared In `PBDataLoggingService.h`. `PBDataLoggingTypeInt` Data logging datatypes. Declared In `PBDataLoggingService.h`. #### Declared In `PBDataLoggingService.h` --- # PBErrorCode Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/PBErrorCode/ | Declared in | PBErrors.h | ### PBErrorCode PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. #### Definition `typedef NS_ENUM(NSInteger, PBErrorCode ) {    PBErrorCodeNoSupportedProtocolsInInfoPlist,    PBErrorCodeErrorCreatingSession,    PBErrorCodeOutputStreamError,    PBErrorCodeInputStreamError,    PBErrorCodeSendMessageTimeout,    PBErrorCodeEndpointDisallowed,    PBErrorCodeVersionInfoTimeout,    PBErrorCodeCannotCreateSessionWhileClosing,    PBErrorCodeNoAppUUID,    PBErrorCodeAppMessageRejected,    PBErrorCodeAppMessageAckTimeout,    PBErrorCodeDictionaryUnsupportedKeyClass,    PBErrorCodeDictionaryUnsupportedValueClass,    PBErrorCodeDictionaryInternalConsistency,    PBErrorCodeInvalidArguments,    PBErrorCodePebbleAppUnreachable,    PBErrorCodePebbleAppRequestTimeout,    PBErrorCodePebbleAppEmptyResponse,    PBErrorCodePebbleAppUnexpectedResponse,    PBErrorCodeServerNotAvailable,    PBErrorCodeDownloadFailed,    PBErrorCodePebbleNotConnected,    PBErrorCodeMessageTooLong,    PBErrorCodeBluetoothDisabled,    PBErrorCodeBluetoothLowEnergyHandshakeFailed,    PBErrorCodeFirmwareUpdateMetadataInvalid,    PBErrorCodeBluetoothLowEnergyFailedToConnect,    PBErrorCodeBluetoothLowEnergyUnfaithful,    PBErrorCodeTransportClosedError,    PBErrorCodeStreamInternalError, };` #### Constants `PBErrorCodeNoSupportedProtocolsInInfoPlist` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeErrorCreatingSession` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeOutputStreamError` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeInputStreamError` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeSendMessageTimeout` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeEndpointDisallowed` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeVersionInfoTimeout` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeCannotCreateSessionWhileClosing` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeNoAppUUID` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeAppMessageRejected` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeAppMessageAckTimeout` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeDictionaryUnsupportedKeyClass` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeDictionaryUnsupportedValueClass` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeDictionaryInternalConsistency` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeInvalidArguments` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodePebbleAppUnreachable` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodePebbleAppRequestTimeout` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodePebbleAppEmptyResponse` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodePebbleAppUnexpectedResponse` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeServerNotAvailable` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeDownloadFailed` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodePebbleNotConnected` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeMessageTooLong` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeBluetoothDisabled` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeBluetoothLowEnergyHandshakeFailed` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeFirmwareUpdateMetadataInvalid` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeBluetoothLowEnergyFailedToConnect` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeBluetoothLowEnergyUnfaithful` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeTransportClosedError` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. `PBErrorCodeStreamInternalError` PebbleKit Error codes. Inspect the localizedDescription of any NSError objects for a description. Declared In `PBErrors.h`. #### Declared In `PBErrors.h` --- # PBFirmwareMetadata Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata/ | Inherits from | NSObject | | Declared in | PBFirmwareMetadata.h | ## Overview Metainformation about a firmware running in a watch. The information contains the firmware [version](#//api/name/version), the firmware hardware platform, and other values. ### version The version of the firmware. `@property (nonatomic, readonly) PBFirmwareVersion *version` #### See Also - `PBFirmwareVersion` #### Declared In `PBFirmwareMetadata.h` ### isRecoveryFirmware YES if the firmware is a recovery firmware, NO if it is a regular firmware. `@property (nonatomic, readonly) BOOL isRecoveryFirmware` #### Declared In `PBFirmwareMetadata.h` ### hardwarePlatform The hardware platform variant with which the firmware is compatible. `@property (nonatomic, readonly) FirmwareMetadataPlatform hardwarePlatform` #### Declared In `PBFirmwareMetadata.h` ### + hardwarePlatformToString: Converts a [FirmwareMetadataPlatform](../../Constants/FirmwareMetadataPlatform/) value to a string. `+ (NSString *)hardwarePlatformToString:(FirmwareMetadataPlatform)hardwarePlatform` #### Parameters _hardwarePlatform_The hardware platform value to convert #### Return Value A string of the hardware platform #### Declared In `PBFirmwareMetadata.h` ### + stringToHardwarePlatform: Converts an NSString hardware platform string to the corresponding [FirmwareMetadataPlatform](../../Constants/FirmwareMetadataPlatform/) value. `+ (FirmwareMetadataPlatform)stringToHardwarePlatform:(NSString *)hardwarePlatformString` #### Parameters _hardwarePlatformString_The string representing the hardware platform. #### Return Value The hardware platform enum value #### Declared In `PBFirmwareMetadata.h` --- # PBFirmwareVersion Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion/ | Inherits from | [PBSemanticVersion](../../Classes/PBSemanticVersion/) : NSObject | | Conforms to | NSCopying | | Declared in | PBFirmwareVersion.h | ## Overview PBFirmwareVersion is a helper class that is able to parse the versionTag string into its components ([major](#//api/name/major), [minor](#//api/name/minor), revision, suffix) and facilitate comparisons between versions. Valid versions follow the following format: `(v)MAJORVERSION(.MINORVERSION(.REVISIONVERSION(-SUFFIX)))` Where the fields MAJOR, MINOR, REVISION are positive, decimal integer numbers and SUFFIX an arbitrary string. MINOR, REVISION, and SUFFIX are optional (nested optionality). Examples of valid versions: - `1` - `v2` - `v3.4-foo` - `v4.1234.99-alpha-beta-gamma` **Note:** For legacy uses in which the legacy properties below are used, the version format is: `(v)OS(.MAJOR(.MINOR(-SUFFIX)))` ## Other Methods ### timestamp The version timestamp. `@property (nonatomic, readonly) uint32_t timestamp` #### Discussion The timestamp is in the final comparison equation, in the -compare: method, after evaluating [major](#//api/name/major), [minor](#//api/name/minor), and revision components. #### Declared In `PBFirmwareVersion.h` ### commitHash The version’s git commit hash. `@property (nonatomic, readonly, copy, nullable) NSString *commitHash` #### Discussion The commit hash is not used in the -compare: method. #### Declared In `PBFirmwareVersion.h` ### tag The version’s git tag. `@property (nonatomic, readonly, copy) NSString *tag` #### Declared In `PBFirmwareVersion.h` ### + firmwareVersionWithString: Creates a PBFirmwareVersion object given a [tag](#//api/name/tag) string. `+ (nullable instancetype)firmwareVersionWithString:(NSString *)tag` #### Parameters _tag_The firmware version string to parse. #### Declared In `PBFirmwareVersion.h` ### + firmwareVersionWithTag:commitHash:timestamp: Creates a PBFirmwareVersion object given a [tag](#//api/name/tag) string and [timestamp](#//api/name/timestamp). `+ (nullable instancetype)firmwareVersionWithTag:(NSString *)tag commitHash:(nullable NSString *)commitHash timestamp:(uint32_t)timestamp` #### Parameters _tag_The firmare version string to parse._commitHash_A version’s commit hash. Can be nil._timestamp_The version timestamp. #### Declared In `PBFirmwareVersion.h` ### + firmwareVersionWithMajor:minor:revision:suffix:commitHash:timestamp: Creates a PBFirmwareVersion object given its components. `+ (instancetype)firmwareVersionWithMajor:(NSInteger)major minor:(NSInteger)minor revision:(NSInteger)revision suffix:(nullable NSString *)suffix commitHash:(nullable NSString *)commitHash timestamp:(uint32_t)timestamp` #### Parameters _major_The version major number._minor_The version minor number._revision_The version revision number._suffix_The version suffix. Can be nil._commitHash_A version’s commit hash. Can be nil._timestamp_The version timestamp. #### Discussion The [tag](#//api/name/tag) string will be set to the canonical format vMAJOR.MINOR.REVISION(-SUFFIX). #### Declared In `PBFirmwareVersion.h` ## Legacy Methods ### os The OS version component. ( **Deprecated:** Use [PBSemanticVersion majorVersion]) `@property (nonatomic, readonly) NSInteger os` #### Declared In `PBFirmwareVersion+Legacy.h` ### major The major version component. ( **Deprecated:** Use [PBSemanticVersion minorVersion]) `@property (nonatomic, readonly) NSInteger major` #### Discussion **Note:** If a version [tag](#//api/name/tag) string does not contain a major component, it will be set to 0. #### Declared In `PBFirmwareVersion+Legacy.h` ### minor The minor version component. ( **Deprecated:** Use [PBSemanticVersion revisionVersion]) `@property (nonatomic, readonly) NSInteger minor` #### Discussion **Note:** If a version [tag](#//api/name/tag) string does not contain a [major](#//api/name/major) component, it will be set to 0. #### Declared In `PBFirmwareVersion+Legacy.h` ### + firmwareVersionWithOS:major:minor:suffix:commitHash:timestamp: Creates a PBFirmwareVersion object given its components. ( **Deprecated:** Use +[PBFirmwareVersion firmwareVersionWithMajor:minor:revision:suffix:commitHash:timestamp:]) `+ (nullable instancetype)firmwareVersionWithOS:(NSInteger)os major:(NSInteger)major minor:(NSInteger)minor suffix:(nullable NSString *)suffix commitHash:(nullable NSString *)commitHash timestamp:(uint32_t)timestamp` #### Parameters _os_The version major number._major_The version minor number._minor_The version revision number._suffix_The version suffix. Can be nil._commitHash_A version’s commit hash. Can be nil._timestamp_The version timestamp. #### Discussion The [tag](#//api/name/tag) string will be set to the canonical format vMAJOR.MINOR.REVISION(-SUFFIX). #### Declared In `PBFirmwareVersion+Legacy.h` --- # PBPebbleCentral Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral/ | Inherits from | NSObject | | Declared in | PBPebbleCentral.h | ## Overview PebbleCentral plays the central role for client iOS apps (e.g. RunKeeper). ## Other Methods ### connectedWatches The watches that are currently connected. Do not cache the array because it can change over time. `@property (nonatomic, readonly, copy) NSArray *connectedWatches` #### Declared In `PBPebbleCentral.h` ### registeredWatches The watches that are stored in the user preferences of the application. `@property (nonatomic, readonly, copy) NSArray *registeredWatches` #### Declared In `PBPebbleCentral.h` ### delegate The central’s delegate. `@property (nonatomic, readwrite, weak) id __nullable delegate` #### Declared In `PBPebbleCentral.h` ### appUUID Identifier of the watch application this companion app communicates with. `@property (nonatomic, copy) NSUUID *appUUID` #### Discussion The identifier is used to make sure that app message and data logging communications arrive to the right companion watch app in the watch (and not to another app). For most app message methods there is are two variants: one that does not take an UUID parameter and one that does (for example, -appMessagesAddReceiveUpdateHandler: vs -appMessagesAddReceiveUpdateHandler:withUUID:). The methods that do not take an UUID, will use the UUID as set prior to this property. **Note:** The UUID needs to be set before using either app message or data logging. #### Declared In `PBPebbleCentral.h` ### appUUIDs The list of App-UUIDs this PebbleCentral wants to talk to. `@property (nonatomic, copy) NSSet *appUUIDs` #### See Also - `- addAppUUID:` #### Declared In `PBPebbleCentral.h` ### – addAppUUID: Registers a new App-UUID with [appUUIDs](#//api/name/appUUIDs). `- (void)addAppUUID:(NSUUID *)appUUID` #### Parameters _appUUID_The app UUID to register. #### See Also - `@property appUUIDs` #### Declared In `PBPebbleCentral.h` ### – run Registers and announces internal Bluetooth services. Might cause a dialog to allow this app to talk to other devices. `- (void)run` #### Declared In `PBPebbleCentral.h` ### – isMobileAppInstalled Determines if the Pebble iOS app is installed in the device. `- (BOOL)isMobileAppInstalled` #### Return Value `YES` if the Pebble iOS app is installed, `NO` if it is not installed. #### Discussion **Note:** Since iOS 9.0 you have to add “pebble” to `LSApplicationQueriesSchemes` in your application `Info.plist` or this method will return `NO` all the time. #### Declared In `PBPebbleCentral.h` ### – installMobileApp Redirects to Pebble in the App Store, so the user can install the app. `- (void)installMobileApp` #### Declared In `PBPebbleCentral.h` ### – unregisterAllWatches Wipes out the data associated with the registered watches, that is stored on the phone. `- (void)unregisterAllWatches` #### Declared In `PBPebbleCentral.h` ### – lastConnectedWatch Returns the most recently connected watch from the [registeredWatches](#//api/name/registeredWatches) array. `- (PBWatch *__nullable)lastConnectedWatch` #### Declared In `PBPebbleCentral.h` ### – dataLoggingServiceForAppUUID: Returns the DataLoggingService for a (previously registered) [appUUID](#//api/name/appUUID) `- (PBDataLoggingService *__nullable)dataLoggingServiceForAppUUID:(NSUUID *)appUUID` #### Parameters _appUUID_The app UUID to recover the data logging service. #### Declared In `PBPebbleCentral.h` ## DefaultCentral Methods ### + defaultCentral The default central singleton instance. `+ (instancetype)defaultCentral` #### Return Value The default central singleton instance. #### Declared In `PBPebbleCentral+DefaultCentral.h` ## Legacy Methods ### – hasValidAppUUID Indicates if the central has been correctly configured with an app UUID. ( **Deprecated:** Use `appUUID != nil` instead.) `- (BOOL)hasValidAppUUID` #### Declared In `PBPebbleCentral+Legacy.h` ### + setDebugLogsEnabled: Enables or disables debug logging for PebbleKit. ( **Deprecated:** Use `+[PBPebbleKitLogging setLogLevel:]` instead.) `+ (void)setDebugLogsEnabled:(BOOL)logsEnabled` #### Parameters _logsEnabled_Whether logging should be enabled or not. #### Declared In `PBPebbleCentral+Legacy.h` ### + setLogLevel: Configures which events should be logged. ( **Deprecated:** Use `+[PBPebbleKitLogging setLogLevel:]` instead.) `+ (void)setLogLevel:(PBPebbleKitLogLevel)logLevel` #### Parameters _logLevel_One of the values in PBPebbleKitLogLevel, which will be the maximum level that will be logged. You can use PBPebbleKitLogLevelOff to disable all logging. #### Declared In `PBPebbleCentral+Legacy.h` ### dataLoggingService Use [`dataLoggingServiceForAppUUID:`](#//api/name/dataLoggingServiceForAppUUID:) instead. ( **Deprecated:** Use `dataLoggingServiceForAppUUID:` instead.) `@property (nonatomic, readonly) PBDataLoggingService *dataLoggingService` #### Declared In `PBPebbleCentral+Legacy.h` --- # PBPebbleCentralDelegate Protocol Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate/ | Conforms to | NSObject | | Declared in | PBPebbleCentral.h | ### – pebbleCentral:watchDidConnect:isNew: The Pebble Central responsible for calling the delegate method. `- (void)pebbleCentral:(PBPebbleCentral *)central watchDidConnect:(PBWatch *)watch isNew:(BOOL)isNew` #### Parameters _central_The Pebble Central responsible for calling the delegate method._watch_The PBWatch object representing the watch that was connected._isNew_YES if the watch has been connected for the first time since the app has been installed or NO if not. #### Declared In `PBPebbleCentral.h` ### – pebbleCentral:watchDidDisconnect: The Pebble Central responsible for calling the delegate method. `- (void)pebbleCentral:(PBPebbleCentral *)central watchDidDisconnect:(PBWatch *)watch` #### Parameters _central_The Pebble Central responsible for calling the delegate method._watch_The PBWatch object representing the watch that was disconnected. #### Declared In `PBPebbleCentral.h` --- # PBPebbleKitLogFlag Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogFlag/ | Declared in | PBLog+Public.h | ### PBPebbleKitLogFlag #### Definition `typedef NS_OPTIONS(NSUInteger, PBPebbleKitLogFlag ) {    PBPebbleKitLogFlagError = ( 1 < < 0 ),    PBPebbleKitLogFlagWarning = ( 1 < < 1 ),    PBPebbleKitLogFlagInfo = ( 1 < < 2 ),    PBPebbleKitLogFlagDebug = ( 1 < < 3 ),    PBPebbleKitLogFlagVerbose = ( 1 < < 4 ), };` #### Constants `PBPebbleKitLogFlagError` Declared In `PBLog+Public.h`. `PBPebbleKitLogFlagWarning` Declared In `PBLog+Public.h`. `PBPebbleKitLogFlagInfo` Declared In `PBLog+Public.h`. `PBPebbleKitLogFlagDebug` Declared In `PBLog+Public.h`. `PBPebbleKitLogFlagVerbose` Declared In `PBLog+Public.h`. --- # PBPebbleKitLogging Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging/ | Inherits from | NSObject | | Declared in | PBLog+Public.h | ## Overview Every log produced by PebbleKit will be sent through this type. By default the logs are simply sent to `NSLog`, but the callback can be overriden to send the logs to another logging facility. The maximum log level can be also modified. The default callback handles the log level automatically, but any customized logging callbacks will need to handle the log level themselves. It is advised to call these methods before making any other calls to PebbleKit, for example in your AppDelegate’s `application:willFinishLaunchingWithOptions:` before state restoration happens. ### + setLogLevel: Configures which events should be logged. `+ (void)setLogLevel:(PBPebbleKitLogLevel)logLevel` #### Parameters _logLevel_One of the values in PBPebbleKitLogLevel, which will be the maximum level that will be logged. You can use PBPebbleKitLogLevelOff to disable all logging. #### Declared In `PBLog+Public.h` ### + setLoggingCallback: Sets a [new](#//api/name/new) logging callback that will be invoked for each of the log records emitted by PebbleKit. `+ (void)setLoggingCallback:(nullable PBPebbleKitLoggingCallback)loggingCallback` #### Parameters _loggingCallback_A block that will be invoked for each of the log records emitted by PebbleKit. Set this value to nil to return to the default NSLog logging. #### Declared In `PBLog+Public.h` ### + new You should not create instances of this type. `+ (instancetype)new` #### Discussion **Note:** You should not create instances of this type. #### Declared In `PBLog+Public.h` ### – init You should not create instances of this type. `- (instancetype)init` #### Discussion **Note:** You should not create instances of this type. #### Declared In `PBLog+Public.h` --- # PBPebbleKitLoggingCallback Block Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Blocks/PBPebbleKitLoggingCallback/ | Declared in | PBLog+Public.h | #### Block Definition ### PBPebbleKitLoggingCallback A logging callback invoked whenever PebbleKit wants to add some message to the log. `typedef void (^PBPebbleKitLoggingCallback) (BOOL asynchronous, PBPebbleKitLogLevel level, PBPebbleKitLogFlag flag, NSInteger context, const char, * file, const char, * function, NSUInteger line, id __nullable, tag ,, NSString *format, va_list args)` #### Discussion It’s important to notice that your logging callback should not block if possible, unless the asynchronous flag is NO, in which case blocking is allowed. **Note:** See CocoaLumberjack documentation for more information about the usage of the paramaters and what they exactly mean: [https://github.com/CocoaLumberjack/CocoaLumberjack/tree/master/Documentation](https://github.com/CocoaLumberjack/CocoaLumberjack/tree/master/Documentation) #### Declared In `PBLog+Public.h` --- # PBPebbleKitLogLevel Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogLevel/ | Declared in | PBLog+Public.h | ### PBPebbleKitLogLevel #### Definition `typedef NS_ENUM(NSUInteger, PBPebbleKitLogLevel ) {    PBPebbleKitLogLevelOff = 0,    PBPebbleKitLogLevelError = ( PBPebbleKitLogFlagError ),    PBPebbleKitLogLevelWarning = ( PBPebbleKitLogLevelError | PBPebbleKitLogFlagWarning ),    PBPebbleKitLogLevelInfo = ( PBPebbleKitLogLevelWarning | PBPebbleKitLogFlagInfo ),    PBPebbleKitLogLevelDebug = ( PBPebbleKitLogLevelInfo | PBPebbleKitLogFlagDebug ),    PBPebbleKitLogLevelVerbose = ( PBPebbleKitLogLevelDebug | PBPebbleKitLogFlagVerbose ),    PBPebbleKitLogLevelAll = 0 xffffffff, };` #### Constants `PBPebbleKitLogLevelOff` Declared In `PBLog+Public.h`. `PBPebbleKitLogLevelError` Declared In `PBLog+Public.h`. `PBPebbleKitLogLevelWarning` Declared In `PBLog+Public.h`. `PBPebbleKitLogLevelInfo` Declared In `PBLog+Public.h`. `PBPebbleKitLogLevelDebug` Declared In `PBLog+Public.h`. `PBPebbleKitLogLevelVerbose` Declared In `PBLog+Public.h`. `PBPebbleKitLogLevelAll` Declared In `PBLog+Public.h`. --- # PBRemoteProtocolCapabilitiesFlags Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/PBRemoteProtocolCapabilitiesFlags/ | Declared in | PBVersionInfo.h | ### PBRemoteProtocolCapabilitiesFlags #### Definition `typedef NS_OPTIONS(uint64_t, PBRemoteProtocolCapabilitiesFlags ) {    PBRemoteProtocolCapabilitiesFlagsAppRunState = 1 < < 0,    PBRemoteProtocolCapabilitiesFlagsInfiniteLogDumping = 1 < < 1,    PBRemoteProtocolCapabilitiesFlagsUpdateMusicProtocol = 1 < < 2,    PBRemoteProtocolCapabilitiesFlagsExtendedNotificationService = 1 < < 3,    PBRemoteProtocolCapabilitiesFlagsLanguagePacksSupported = 1 < < 4,    PBRemoteProtocolCapabilitiesFlagsAppMessage8kSupported = 1 < < 5,    PBRemoteProtocolCapabilitiesFlagsInsightsSupport = 1 < < 6,    PBRemoteProtocolCapabilitiesFlagsThirdPartyVoiceSupported = 1 < < 7,    PBRemoteProtocolCapabilitiesFlagsSendTextSupported = 1 < < 8,    PBRemoteProtocolCapabilitiesFlagsNotificationsFilteringSupported = 1 < < 9,    PBRemoteProtocolCapabilitiesFlagsUnreadCoredumpSupport = 1 < < 10,    PBRemoteProtocolCapabilitiesFlagsWeatherApp2 = 1 < < 11,    PBRemoteProtocolCapabilitiesFlagsReminderApp = 1 < < 12,    PBRemoteProtocolCapabilitiesFlagsWorkoutApp = 1 < < 13,    PBRemoteProtocolCapabilitiesFlagsJSBytecodeVersionAppended = 1 < < 16, };` #### Constants `PBRemoteProtocolCapabilitiesFlagsAppRunState` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsInfiniteLogDumping` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsUpdateMusicProtocol` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsExtendedNotificationService` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsLanguagePacksSupported` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsAppMessage8kSupported` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsInsightsSupport` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsThirdPartyVoiceSupported` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsSendTextSupported` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsNotificationsFilteringSupported` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsUnreadCoredumpSupport` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsWeatherApp2` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsReminderApp` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsWorkoutApp` Declared In `PBVersionInfo.h`. `PBRemoteProtocolCapabilitiesFlagsJSBytecodeVersionAppended` Declared In `PBVersionInfo.h`. --- # PBResourceMetadata Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBResourceMetadata/ | Inherits from | NSObject | | Declared in | PBResourceMetadata.h | ## Overview Instances of PBResourceMetadata describe (system) resources. ### timestamp The version of the resources as the Epoch timestamp of the Git commit. `@property (nonatomic, readonly) uint32_t timestamp` #### Declared In `PBResourceMetadata.h` ### crc The checksum of the resources. `@property (nonatomic, readonly) uint32_t crc` #### Declared In `PBResourceMetadata.h` --- # PBSemanticVersion Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion/ | Inherits from | NSObject | | Declared in | PBSemanticVersion.h | ## Overview A version number that follows the rules of semantic versioning. The version number is supposed to have three components and an optional [suffix](#//api/name/suffix). ### majorVersion The version major number `@property (nonatomic, assign, readonly) NSUInteger majorVersion` #### Declared In `PBSemanticVersion.h` ### minorVersion The version minor number `@property (nonatomic, assign, readonly) NSUInteger minorVersion` #### Declared In `PBSemanticVersion.h` ### revisionVersion The version revision number `@property (nonatomic, assign, readonly) NSUInteger revisionVersion` #### Declared In `PBSemanticVersion.h` ### suffix The version suffix string. `@property (nonatomic, copy, readonly) NSString *suffix` #### Discussion **Note:** If a version tag string does not contain a suffix, it will be set to an empty string. The release suffix is not used in the [compare:](#//api/name/compare:) or -isEqual: methods. #### Declared In `PBSemanticVersion.h` ### – initWithVersionString: Returns a new instance by parsing the input. `- (nullable instancetype)initWithVersionString:(NSString *)versionString` #### Parameters _versionString_Expected format: major.minor.revision-sffix. Example: “2.0.1-rc2” If a parsing error occurs, nil will be returned. #### Return Value A version or `nil` if some error happen. #### Declared In `PBSemanticVersion.h` ### – initWithMajor:minor:revision:suffix: Returns a new instance by using the given components. `- (instancetype)initWithMajor:(NSUInteger)major minor:(NSUInteger)minor revision:(NSUInteger)revision suffix:(nullable NSString *)suffix` #### Parameters _major_The version major number._minor_The version minor number._revision_The version revision number._suffix_The version suffix. Can be nil. #### Declared In `PBSemanticVersion.h` ### – compare: Compares the receiver to another version object. `- (NSComparisonResult)compare:(PBSemanticVersion *)aVersion` #### Parameters _aVersion_The version to compare with the receiver. #### Return Value `NSOrderedAscending` if the value of aVersion is greater than the receiver’s, `NSOrderedSame` if they’re equal, and `NSOrderedDescending` if the value of `aVersion` is less than the receiver’s. #### Declared In `PBSemanticVersion.h` ### – isEqualOrNewer: Returns if the the receiver is equal or newer than the given version. `- (BOOL)isEqualOrNewer:(PBSemanticVersion *)other` #### Parameters _other_Version number to compare against. #### Return Value `YES` if the receiver is equal or newer. `NO` otherwise. #### Declared In `PBSemanticVersion.h` ### – isNewer: Returns if the receiver is strictly newer than the given version. `- (BOOL)isNewer:(PBSemanticVersion *)other` #### Parameters _other_Version number to compare against. #### Return Value `YES` if the receiver is stricly newer. `NO` otherwise. #### Declared In `PBSemanticVersion.h` --- # PBSportsUpdate Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate/ | Inherits from | NSObject | | Declared in | PBWatch+Sports.h | ## Overview Packages a UI update of the sports app. This container class includes new values of time, distance, pace or speed, heart rate, and a custom label/value. To send the values to the watch use [[PBSportsUpdate updateWatch:completion:]](#//api/name/updateWatch:completion:). The class is mutable, so consumers can update the values and send the same instance again. Modifying the values of this class will not send the values. The consumer needs to invoke [[PBSportsUpdate updateWatch:completion:]](#//api/name/updateWatch:completion:) every [time](#//api/name/time) the UI needs to be updated. ## Other Methods ### time The current time in seconds. `@property (nonatomic) NSTimeInterval time` #### Discussion The possible range is currently limited from -35999 to 35999, inclusive (±9h 59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Hours, minutes and seconds will be separated by colons. The hours value will only appear if the value is more than 1 hour. The decimal part will be discarded for the presentation. #### Declared In `PBWatch+Sports.h` ### distance The current distance in kilometers or miles. `@property (nonatomic) float distance` #### Discussion The possible range is currently limited from -99.9 to 99.9, inclusive. Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a decimal number in the UI. The decimal part will be rounded to one digit. The unit of distance is dependent on the current unit setting. #### See Also - `[PBWatch sportsAppSetMetric:onSent:]` #### Declared In `PBWatch+Sports.h` ### pace The current pace in seconds per kilometer or seconds per mile. `@property (nonatomic) NSTimeInterval pace` #### Discussion The possible range is currently limited from -3599 to 3599, inclusive (±59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Minutes and seconds will be separated by colons. The decimal part will be discarded for the presentation. Currently pace and [speed](#//api/name/speed) cannot be presented at the same [time](#//api/name/time). Setting [speed](#//api/name/speed) will discard the value set through pace. #### Declared In `PBWatch+Sports.h` ### speed The current speed in kilometers per hour or miles per hour. `@property (nonatomic) float speed` #### Discussion The possible range is currently limited from -99.9 to 99.9, inclusive. Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a decimal number in the UI. The decimal part will be rounded to one digit. Currently [pace](#//api/name/pace) and speed cannot be presented at the same [time](#//api/name/time). Setting [pace](#//api/name/pace) will discard the value set through speed. #### Declared In `PBWatch+Sports.h` ### heartRate The current heart rate in beats per minute. `@property (nonatomic) uint8_t heartRate` #### Discussion Currently there’s no way to stop sending heart rate values if one heart rate value was sent. The last value will be shown in the UI. If the heart rate has never been set before, this property will return zero. #### Declared In `PBWatch+Sports.h` ### customLabel A custom label to show in the sports UI. `@property (nonatomic, copy, nullable) NSString *customLabel` #### Discussion The maximum number of characters is ~10, but this maximum is not enforced. The label will be sent in upper case to the watch. To be sent, both customLabel and [customValue](#//api/name/customValue) have to be set to non-nil values. #### Declared In `PBWatch+Sports.h` ### customValue A custom value to show in the sports UI. `@property (nonatomic, copy, nullable) NSString *customValue` #### Discussion The maximum number of characters is ~8, but the maximum is not enforced. To be sent, both customValue and [customLabel](#//api/name/customLabel) have to be set to non-nil values. #### Declared In `PBWatch+Sports.h` ### appMessageDictionary Creates an update [dictionary](#//api/name/dictionary) from the receiver, that can be used with [`[PBWatch sportsAppUpdate:onSent:]`](../../Classes/PBWatch/#//api/name/sportsAppUpdate:onSent:). `@property (nonatomic, readonly) NSDictionary *appMessageDictionary` #### Discussion You should prefer using [`[PBSportsUpdate updateWatch:completion:]`](#//api/name/updateWatch:completion:). #### See Also - `[PBWatch sportsAppUpdate:onSent:]` #### Declared In `PBWatch+Sports.h` ### – updateWatch:completion: Sends an update to the watch. `- (void)updateWatch:(PBWatch *)watch completion:(void ( ^ __nullable ) ( NSError *__nullable error ))completion` #### Parameters _watch_The watch to send the update to._completion_Block that will be called when the update either succeeds or fails. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion The method tries to send the minimal set of changes since the last [time](#//api/name/time) the method was used, to try to minimize communication with the watch. #### Declared In `PBWatch+Sports.h` ### + timeStringFromSeconds: Creates a formatted [time](#//api/name/time) string from a total seconds value, formatted as “h:mm:ss”. `+ (NSString *)timeStringFromSeconds:(NSTimeInterval)seconds` #### Parameters _seconds_The number of seconds from which to create the time string. #### Return Value Formatted [time](#//api/name/time) as “h:mm:ss” #### Discussion For example, supplying the value 3930.0f seconds will return @“1:05:30”. #### Declared In `PBWatch+Sports.h` ### + decimalStringFromFloat: Creates a formatted decimal string with one decimal number. `+ (NSString *)decimalStringFromFloat:(float)decimal` #### Parameters _decimal_The decimal number to format as a string. #### Return Value The formatted decimal number. #### Discussion For example, supplying the value 13.42f will return @“13.4”. #### Declared In `PBWatch+Sports.h` ## Deprecated Methods ### data General purpose data variable. ( **Deprecated:** Prefer either `pace` or `speed`.) `@property (nonatomic) float data` #### Discussion **Note:** The original property design is very broken and it has been deprecated in favor of both [pace](#//api/name/pace) and [speed](#//api/name/speed). This value will act as [pace](#//api/name/pace) from now on. The possible range is currently limited from -3599 to 3599, inclusive (±59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Minutes and seconds will be separated by colons. The decimal part will be discarded for the presentation. Setting a value in either [pace](#//api/name/pace) or [speed](#//api/name/speed) will discard the value set through data. #### See Also - `[PBWatch sportsAppSetLabel:onSent:]` #### Declared In `PBWatch+Sports.h` ### + updateWithTime:distance:data: Creates a new `PBSportsUpdate` with the given [time](#//api/name/time), [distance](#//api/name/distance) and [data](#//api/name/data). ( **Deprecated:** Prefer -[PBSportsUpdate init] and filling the properties.) `+ (instancetype)updateWithTime:(NSTimeInterval)time distance:(float)distance data:(float)data` #### Parameters _time_The current time in seconds._distance_The current distance in kilometers or miles._data_General purpose data variable. #### Return Value A new `PBSportsUpdate` with the given [time](#//api/name/time), [distance](#//api/name/distance) and [data](#//api/name/data). #### Declared In `PBWatch+Sports.h` ### – dictionary Creates an update dictionary from the receiver that can be used with [`[PBWatch sportsAppUpdate:onSent:]`](../../Classes/PBWatch/#//api/name/sportsAppUpdate:onSent:). ( **Deprecated:** Prefer using -[PBSportsUpdate appMessageDictionary].) `- (NSDictionary *)dictionary` #### See Also - `[PBWatch sportsAppUpdate:onSent:]` #### Declared In `PBWatch+Sports.h` ### + timeStringFromFloat: Creates a formatted [time](#//api/name/time) string from a total seconds value, formatted as “h:mm:ss”. ( **Deprecated:** Prefer +[PBSportsUpdate timeStringFromSeconds:].) `+ (NSString *)timeStringFromFloat:(float)seconds` #### Parameters _seconds_The number of seconds from which to create the time string. #### Return Value Formatted [time](#//api/name/time) as “h:mm:ss” #### Discussion For example, supplying the value 3930.0f seconds will return @“1:05:30”. #### Declared In `PBWatch+Sports.h` --- # PBUnitSystem Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/PBUnitSystem/ | Declared in | PBWatch+Sports.h | | References | -[PBWatch(Sports) sportsAppSetUnitSystem:onSent:] | ### PBUnitSystem The unit system available for the Sports app. #### Definition `typedef NS_ENUM(NSInteger, PBUnitSystem ) {    PBUnitSystemMetric,    PBUnitSystemImperial, };` #### Constants `PBUnitSystemMetric` The metric system. Use kilometers, kilometers per hour and minutes per kilometer. Declared In `PBWatch+Sports.h`. `PBUnitSystemImperial` The imperial system. Use miles, miles per hour and minutes per mile. Declared In `PBWatch+Sports.h`. #### See Also - `[PBWatch(Sports) sportsAppSetUnitSystem:onSent:]` #### Declared In `PBWatch+Sports.h` --- # PBVersionInfo Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo/ | Inherits from | NSObject | | Declared in | PBVersionInfo.h | ## Overview An instance of PBVersionInfo describes all the different system software (Pebble OS) on a Pebble watch and other auxiliary information. ### runningFirmwareMetadata The metadata of the firmware that is running. `@property (nonatomic, readonly, strong, nullable) PBFirmwareMetadata *runningFirmwareMetadata` #### Declared In `PBVersionInfo.h` ### recoveryFirmwareMetadata The metadata of the recovery firmware that is in storage. `@property (nonatomic, readonly, strong, nullable) PBFirmwareMetadata *recoveryFirmwareMetadata` #### Declared In `PBVersionInfo.h` ### systemResources The metadata of the system resources. `@property (nonatomic, readonly, strong) PBResourceMetadata *systemResources` #### Declared In `PBVersionInfo.h` ### bootloaderVersion The version of the bootloader. `@property (nonatomic, readonly) UInt32 bootloaderVersion` #### Declared In `PBVersionInfo.h` ### hardwareVersion The version of the hardware. `@property (nonatomic, readonly, strong) NSString *hardwareVersion` #### Declared In `PBVersionInfo.h` ### serialNumber The serial number. `@property (nonatomic, readonly, strong) NSString *serialNumber` #### Declared In `PBVersionInfo.h` ### deviceAddress The current Bluetooth device address. `@property (nonatomic, readonly, strong) NSData *deviceAddress` #### Declared In `PBVersionInfo.h` ### remoteProtocolCapabilitiesFlags Flags for the supported protocol features of the watch. `@property (nonatomic, readonly, assign) PBRemoteProtocolCapabilitiesFlags remoteProtocolCapabilitiesFlags` #### Declared In `PBVersionInfo.h` ### hasBeenUnfaithful Indicates if the previous connection for the watch was to this device. `@property (nonatomic, readonly, assign) BOOL hasBeenUnfaithful` #### Discussion If the watch has been connected to this device, but connects to other device before connecting again to this device, the value will be YES. #### Declared In `PBVersionInfo.h` ### – hasRecoveryFirmware YES if valid recovery firmware is installed, NO if not. `- (BOOL)hasRecoveryFirmware` #### Declared In `PBVersionInfo.h` ### – hasSystemResources YES if valid system resources are installed, NO if not. `- (BOOL)hasSystemResources` #### Declared In `PBVersionInfo.h` ### – appMessagesSupported YES if app messages are supported, NO if not. `- (BOOL)appMessagesSupported` #### Declared In `PBVersionInfo.h` --- # PBVersionInfoCompletionBlock Block Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Blocks/PBVersionInfoCompletionBlock/ | Declared in | PBWatch+Version.h | #### Block Definition ### PBVersionInfoCompletionBlock `typedef void (^PBVersionInfoCompletionBlock) (PBWatch *watch, PBVersionInfo *__nullable, versionInfo ), ; PB_EXTERN, NSTimeInterval const, PBVersionInfoResponseTimeout ;, @interface PBWatch, ( Version, ) -, ( void, ) getVersionInfo, : (, PBVersionInfoCompletionBlock ), handler onTimeout, : (, void (, ^__nullable)` --- # PBWatch Class Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch/ | Inherits from | NSObject | | Declared in | PBWatch.h | ## Overview Represents a Pebble watch. ## Other Methods ### central The central that is managing this watch. `@property (nonatomic, weak, readonly) PBPebbleCentral *central` #### Discussion This property is KVO-compliant. #### Declared In `PBWatch.h` ### – isNew YES if the receiver it’s the first time that watch has been seen. `- (BOOL)isNew` #### Discussion This property is KVO-compliant. #### Declared In `PBWatch.h` ### connected YES if the receiver is connected and NO if the receiver is disconnected. `@property (nonatomic, assign, readonly, getter=isConnected) BOOL connected` #### Discussion This property is KVO-compliant. #### Declared In `PBWatch.h` ### name The human-friendly name of the receiver. This is the same name as the user will see in the iOS Bluetooth Settings. `@property (nonatomic, copy, readonly) NSString *name` #### Declared In `PBWatch.h` ### serialNumber The serial number of the receiver. `@property (nonatomic, copy, readonly) NSString *serialNumber` #### Declared In `PBWatch.h` ### versionInfo The versionInfo of the receiver. `@property (nonatomic, strong, readonly) PBVersionInfo *versionInfo` #### See Also - `PBVersionInfo` #### Declared In `PBWatch.h` ### delegate The delegate of the watch that will be notified of disconnections and errors. `@property (nonatomic, weak) id __nullable delegate` #### See Also - `PBWatchDelegate` #### Declared In `PBWatch.h` ### userInfo The userInfo property can be used to associate application specific data with the watch. Note that the application itself is responsible for persisting the information if neccessary. `@property (nonatomic, strong) id __nullable userInfo` #### Declared In `PBWatch.h` ### lastConnectedDate The date when the watch was last known to be [connected](#//api/name/connected). This date will be updated automatically when the watch connects and disconnects. While the watch is being [connected](#//api/name/connected), this date will not be updated. `@property (nonatomic, strong, readonly) NSDate *lastConnectedDate` #### Declared In `PBWatch.h` ### – friendlyDescription Developer-friendly debugging description of the watch. `- (NSString *)friendlyDescription` #### Return Value developer-friendly summary of the receiver, including software and hardware version information, if available. #### Declared In `PBWatch.h` ### – releaseSharedSession Releases the shared session to the watch (if one exists). ( **Deprecated:** Remove usages of this method. The implementation is empty.) `- (void)releaseSharedSession` #### Discussion Depending on availability a per-app dedicated Bluetooth LE based session (CoreBluetooth.framework) will be used to talk to the watch. In other cases a Bluetooth Classic based session (ExternalAccessory.framework) will be used that is shared between all 3rd party iOS apps. Once the user is done using the app/watch-integration, the shared sessions has to be released using this method so it can be used by other apps. #### Declared In `PBWatch.h` ## Golf Methods ### – golfGetIsSupported: Queries the watch whether Golf Messages are supported. `- (void)golfGetIsSupported:(void ( ^ ) ( PBWatch *watch , BOOL isGolfSupported ))fetchedBlock` #### Parameters _fetchedBlock_The block that will be called when the inquiry has finished. The block will be called asynchronously on the queue that was originally used when calling this method. watch: The watch on which the query was performed. isGolfSupported: YES if Golf Messages are supported, NO if not. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Golf.h` ### – golfAppLaunch: Send a command to launch the golf app on the watch that the receiver represents. `- (void)golfAppLaunch:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _onSent_The handler that will be called when the launch command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Golf.h` ### – golfAppKill: Send a command to kill the golf app on the watch that the receiver represents. `- (void)golfAppKill:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _onSent_The handler that will be called when the kill command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Golf.h` ### – golfAppUpdate:onSent: Sends an update to the golf app on the watch that the receiver represents. Must be called from the main thread. `- (void)golfAppUpdate:(NSDictionary *)update onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _update_The update to send. Use one or more keys from PBGolfFrontKey, PBGolfMidKey, PBGolfBackKey, PBGolfHoleKey, or PBGolfParKey. Note that the value for each key MUST be of NSString type._onSent_The handler that will be called when the update has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Declared In `PBWatch+Golf.h` ### – golfAppAddReceiveUpdateHandler: Add a receive handler for incoming Golf updates that are send by the Golf watch application. `- (id)golfAppAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch *watch , GolfAppCommand command ))onReceive` #### Parameters _onReceive_The block that will be called every time a new update message arrives. watch: The receiver of the update. command: The command as sent by the watch. #### Return Value An opaque handle object representing the installed receive handler, that can be used in [golfAppRemoveUpdateHandler:](#//api/name/golfAppRemoveUpdateHandler:) #### Discussion Must be called from the main thread. #### See Also - `GolfAppCommand` - `- golfAppRemoveUpdateHandler:` #### Declared In `PBWatch+Golf.h` ### – golfAppRemoveUpdateHandler: Removes a receive handler that was previously installed using [golfAppAddReceiveUpdateHandler:](#//api/name/golfAppAddReceiveUpdateHandler:) `- (void)golfAppRemoveUpdateHandler:(id)opaqueHandle` #### Parameters _opaqueHandle_The handle object as returned by golfAppAddReceiveUpdateHandler: #### Discussion Must be called from the main thread. #### See Also - `- golfAppAddReceiveUpdateHandler:` #### Declared In `PBWatch+Golf.h` ### – golfSetTitle:icon:onSent: Assigns a custom title and icon to the golf app on the watch. `- (void)golfSetTitle:(NSString *)title icon:(UIImage *)icon onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _title_The custom title (max. 20 bytes of UTF-8 string)_icon_The custom icon (max. 32x32 pixels, black/white only)_onSent_The handler that will be called when the title and icon have been set or timed out. watch: the recipient of the title and icon. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. It is recommended to perform this as the first call after [golfGetIsSupported:](#//api/name/golfGetIsSupported:) to avoid changing the title and icon while it is being displayed in the menu as to avoid confusion. #### Declared In `PBWatch+Golf.h` ## Ping Methods ### – pingWithCookie:onPong:onTimeout: Sends a ping to the watch. `- (void)pingWithCookie:(uint32_t)cookie onPong:(void ( ^ __nullable ) ( PBWatch *watch , uint32_t cookie ))onPong onTimeout:(void ( ^__nullable ) ( PBWatch *watch , uint32_t cookie ))onTimeout` #### Parameters _cookie_A number identifying the ping._onPong_The block handler that will be called when the “pong” reply from the watch has been received._onTimeout_The block handler that will be called when the watch failed to reply in time. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Ping.h` ## SportsDeprecated Methods ### – sportsAppSetLabel:onSent: Send a command to the sports app on the watch that the receiver represents, to set the preferred data label (either PACE or SPEED) and corresponding units. ( **Deprecated:** Prefer using [PBSportsUpdate pace] or [PBSportsUpdate speed].) `- (void)sportsAppSetLabel:(BOOL)isPace onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _isPace_YES to request the pace label or NO to request the speed label._onSent_The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Sports.h` ### – sportsAppSetMetric:onSent: Send a command to the sports app on the watch that the receiver represents, to set the preferred unit system, either metric or imperial. ( **Deprecated:** Prefer using [PBWatch(Sports) sportsAppSetUnitSystem:onSent:]) `- (void)sportsAppSetMetric:(BOOL)isMetric onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _isMetric_YES to request metric units or NO to request imperial units._onSent_The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Sports.h` ## AppMessages Methods ### – appMessagesGetIsSupported: Queries the watch whether AppMessages are supported. `- (void)appMessagesGetIsSupported:(void ( ^ ) ( PBWatch *watch , BOOL isAppMessagesSupported ))fetchedBlock` #### Parameters _fetchedBlock_The block that will be called when the inquiry has finished. The block will be called asynchronously on the queue that was originally used when calling this method. watch: The watch on which the query was performed. isAppMessagesSupported: YES if AppMessages are supported, NO if not. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+AppMessages.h` ### – appMessagesPushUpdate:onSent: Pushes an update to the watch application with UUID as set using [[PBPebbleCentral setAppUUID:]](../../Classes/PBPebbleCentral/#//api/name/appUUID). `- (void)appMessagesPushUpdate:(NSDictionary *)dictionary onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSDictionary *update , NSError *__nullable error ))onSent` #### Parameters _dictionary_Contains the key/value pairs to update. The dictionary can only contain items with an NSNumber key and only contain NSString, NSNumber or NSData values. Use the methods in the NSNumber (stdint) category to create NSNumbers with specific types standard integer types._onSent_The block that will be called when the message was accepted, rejected or timed out. watch: The watch to which the update was sent. update: The dictionary that was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+AppMessages.h` ### – appMessagesPushUpdate:withUUID:onSent: Pushes an update to the watch application with the specified UUID. `- (void)appMessagesPushUpdate:(NSDictionary *)dictionary withUUID:(NSUUID *)appUUID onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSDictionary *update , NSError *__nullable error ))onSent` #### Parameters _dictionary_Contains the key/value pairs to update. The dictionary can only contain items with an NSNumber key and only contain NSString, NSNumber or NSData values. Use the methods in the NSNumber (stdint) category to create NSNumbers with specific types standard integer types._appUUID_The UUID of the watchapp to which the update should be sent._onSent_The block that will be called when the message was accepted, rejected or timed out. watch: The watch to which the update was sent. update: The dictionary that was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+AppMessages.h` ### – appMessagesAddReceiveUpdateHandler: Add a receive handler for incoming updates that are send by the watch application with UUID as set using [[PBPebbleCentral setAppUUID:]](../../Classes/PBPebbleCentral/#//api/name/appUUID). `- (id)appMessagesAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch *watch , NSDictionary *update ))onReceive` #### Parameters _onReceive_The block that will be called every time a new update message arrives. watch: The watch that has sent the update. update: The dictionary containing the values sent by the watch. #### Return Value An opaque handle object representing the installed receive handler, that can be used in [appMessagesRemoveUpdateHandler:](#//api/name/appMessagesRemoveUpdateHandler:) #### Discussion Must be called from the main thread. #### See Also - `- appMessagesRemoveUpdateHandler:` #### Declared In `PBWatch+AppMessages.h` ### – appMessagesAddReceiveUpdateHandler:withUUID: Add a receive handler for incoming updates that are send by the watch application with the specified UUID. `- (id)appMessagesAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch *watch , NSDictionary *update ))onReceive withUUID:(NSUUID *)appUUID` #### Parameters _onReceive_The block that will be called every time a new update message arrives. You should always return YES so than an ACK is sent to the watch. watch: The watch that has sent the update. update: The dictionary containing the values sent by the watch. _appUUID_The UUID of the watchapp for which sent messages should be handled by the onReceive block. #### Return Value An opaque handle object representing the installed receive handler, that can be used in [appMessagesRemoveUpdateHandler:](#//api/name/appMessagesRemoveUpdateHandler:) #### Discussion Must be called from the main thread. #### See Also - `- appMessagesRemoveUpdateHandler:` #### Declared In `PBWatch+AppMessages.h` ### – appMessagesRemoveUpdateHandler: Removes a receive handler that was previously installed using [appMessagesAddReceiveUpdateHandler:](#//api/name/appMessagesAddReceiveUpdateHandler:) `- (void)appMessagesRemoveUpdateHandler:(id)opaqueHandle` #### Parameters _opaqueHandle_The handle object as returned by appMessagesAddReceiveUpdateHandler: #### Discussion Must be called from the main thread. #### See Also - `- appMessagesAddReceiveUpdateHandler:` #### Declared In `PBWatch+AppMessages.h` ### – appMessagesLaunch: Sends a command to launch the watch application with UUID as set using [[PBPebbleCentral setAppUUID:]](../../Classes/PBPebbleCentral/#//api/name/appUUID) `- (void)appMessagesLaunch:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _onSent_The block that will be called after the launch command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+AppMessages.h` ### – appMessagesLaunch:withUUID: Sends a command to launch the watch application with the specified UUID. `- (void)appMessagesLaunch:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent withUUID:(NSUUID *)appUUID` #### Parameters _onSent_The block that will be called after the launch command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. _appUUID_The UUID of the watch application to launch. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+AppMessages.h` ### – appMessagesKill: Sends a command to kill the watch application with UUID as set using [[PBPebbleCentral setAppUUID:]](../../Classes/PBPebbleCentral/#//api/name/appUUID). `- (void)appMessagesKill:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _onSent_The block that will be called after the kill command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+AppMessages.h` ### – appMessagesKill:withUUID: Sends a command to kill the watch application with the specified UUID. `- (void)appMessagesKill:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent withUUID:(NSUUID *)appUUID` #### Parameters _onSent_The block that will be called after the kill command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. _appUUID_The UUID of the watch application to launch. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+AppMessages.h` ## Legacy Methods ### – closeSession: Closes the communication session with the watch. ( **Deprecated:** Use [PBWatch releaseSharedSession]) `- (void)closeSession:(void ( ^ __nullable ) ( void ))onDone` #### Parameters _onDone_Callback block that will be called after the closing of the session has completed. If there is no open session, the onDone block will (also) be executed asynchronously on the calling queue. #### Discussion Since there is only one, shared session for all 3rd party iOS apps, an app should close the session after the user is done using the app/watch-integration, so it can be used by other apps. The communication session is implicitely opened automatically when needed. #### Declared In `PBWatch+Legacy.h` ## Sports Methods ### – sportsGetIsSupported: Queries the watch whether Sports Messages are supported. `- (void)sportsGetIsSupported:(void ( ^ ) ( PBWatch *watch , BOOL isSportsSupported ))fetchedBlock` #### Parameters _fetchedBlock_The block that will be called when the inquiry has finished. The block will be called asynchronously on the queue that was originally used when calling this method. watch: the recipient of the query. isSportsSupported: YES if Sports API is supported, NO otherwise. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Sports.h` ### – sportsAppLaunch: Send a command to launch the sports app on the watch that the receiver represents. `- (void)sportsAppLaunch:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _onSent_The handler that will be called when the launch command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread, and before [sportsAppSetMetric:onSent:](#//api/name/sportsAppSetMetric:onSent:) or [sportsAppUpdate:onSent:](#//api/name/sportsAppUpdate:onSent:). #### Declared In `PBWatch+Sports.h` ### – sportsAppKill: Send a command to kill the sports app on the watch that the receiver represents. `- (void)sportsAppKill:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _onSent_The handler that will be called when the kill command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Sports.h` ### – sportsAppSetUnitSystem:onSent: Send a command to the sports app on the watch that the receiver represents, to set the preferred unit system. `- (void)sportsAppSetUnitSystem:(PBUnitSystem)unitSystem onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _unitSystem_PBUnitSystemMetric to request metric units or PBUnitSystemImperial to request imperial units._onSent_The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Sports.h` ### – sportsAppSetActivityState:onSent: Send a command to the sports app on the watch that the receiver represents, to set the state of the sports activity. Currently only `SportsAppActivityStateRunning` and `SportsAppActivityStatePaused` are supported. `- (void)sportsAppSetActivityState:(SportsAppActivityState)state onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _state_The new sports activity state_onSent_The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. #### Declared In `PBWatch+Sports.h` ### – sportsAppUpdate:onSent: Sends the update to the sports app on the watch that the receiver represents. `- (void)sportsAppUpdate:(NSDictionary *)update onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _update_The update to send._onSent_The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. The valid keys and values for the dictionary are the following: - `PBSportsTimeKey`: the string representing the time. The value must be an `NSString`. The maximum number of characters is about 8. - `PBSportsDistanceKey`: the string representing the distance. The value must be an `NSString`. The maximum number of characters is about 5. - `PBSportsDataKey`: the string representing the pace or the speed. The value must be an `NSString`. The maximum number of characters is about 5. - `PBSportsActivityStateKey`: the state of the activity. The value must be an `NSNumber` created using +[NSNumber(stdint) pb\_numberWithUint8:]. The value must be one of the values from the enum [SportsAppActivityState](../../Constants/SportsAppActivityState/). - `PBSportsIsPaceKey`: whether the value sent in `PBSportsDataKey` is the pace (use value 1) or the speed (use value 0). You don’t need to continously send this value. The value must be an `NSNumber` created using +[NSNumber(stdin) pb\_numberWithUint8:]. - `PBSportsHeartBPMKey`: the current heart rate in beats per minute. The value must be an `NSNumber` created using [NSNumber(stdin) pb\_numberWithUint8:]. - `PBSportsCustomLabelKey`: the string to show as a custom label. The value must be an `NSString`. The maximum number of characters is about 10. The string will be transformed into upper case before being sent to the watch. - `PBSportsCustomValueKey`: the string to show as a custom value. The value must be an `NSString`. The maximum number of characters is about 8. #### Declared In `PBWatch+Sports.h` ### – sportsAppAddReceiveUpdateHandler: Add a receive handler for incoming Sports updates that are send by the Sports watch application. `- (id)sportsAppAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch *watch , SportsAppActivityState state ))onReceive` #### Parameters _onReceive_The block that will be called every time a new update message arrives. watch: The watch that has sent the update. state: The new sports activity state as set by the watch. #### Return Value An opaque handle object representing the installed receive handler, that can be used in [sportsAppRemoveUpdateHandler:](#//api/name/sportsAppRemoveUpdateHandler:). #### Discussion Must be called from the main thread. #### See Also - `SportsAppActivityState` - `- sportsAppRemoveUpdateHandler:` #### Declared In `PBWatch+Sports.h` ### – sportsAppRemoveUpdateHandler: Removes a receive handler that was previously installed using [sportsAppAddReceiveUpdateHandler:](#//api/name/sportsAppAddReceiveUpdateHandler:). `- (void)sportsAppRemoveUpdateHandler:(id)opaqueHandle` #### Parameters _opaqueHandle_The handle object as returned by sportsAppAddReceiveUpdateHandler: #### Discussion Must be called from the main thread. #### See Also - `- sportsAppAddReceiveUpdateHandler:` #### Declared In `PBWatch+Sports.h` ### – sportsSetTitle:icon:onSent: Assigns a custom title and icon to the sports app on the watch. `- (void)sportsSetTitle:(NSString *)title icon:(UIImage *)icon onSent:(void ( ^ __nullable ) ( PBWatch *watch , NSError *__nullable error ))onSent` #### Parameters _title_The custom title (max. 20 bytes of UTF-8 string)_icon_The custom icon (max. 32x32 pixels, black/white only). This image cannot be nil._onSent_The handler that will be called when the title and icon have been set or timed out. watch: the recipient of the custom title and icon. error: nil if the operation was successful, or else an NSError with more information on why it failed. #### Discussion Must be called from the main thread. It is recommended to perform this as the first call after [sportsGetIsSupported:](#//api/name/sportsGetIsSupported:) to avoid changing the title and icon while it is being displayed in the menu as to avoid confusion. #### Declared In `PBWatch+Sports.h` --- # PBWatchDelegate Protocol Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate/ | Conforms to | NSObject | | Declared in | PBWatch.h | ### – watchDidDisconnect: Called when the watch got disconnected. `- (void)watchDidDisconnect:(PBWatch *)watch` #### Parameters _watch_The watch that was disconnected. #### Declared In `PBWatch.h` ### – watch:handleError: Called when the watch caught an error. `- (void)watch:(PBWatch *)watch handleError:(NSError *)error` #### Parameters _watch_The watch that caught the error._error_The error catch by the watch. #### Declared In `PBWatch.h` ### – watchWillResetSession: Called when an internal session is about to be reset. `- (void)watchWillResetSession:(PBWatch *)watch` #### Parameters _watch_The watch that will reset its session. #### Declared In `PBWatch.h` ### – watchDidOpenSession: Called when an internal session is opened `- (void)watchDidOpenSession:(PBWatch *)watch` #### Parameters _watch_The watch that opened a session. #### Declared In `PBWatch.h` ### – watchDidCloseSession: Called when an internal EASession is closed `- (void)watchDidCloseSession:(PBWatch *)watch` #### Parameters _watch_The watch that closed its session. #### Declared In `PBWatch.h` --- # PebbleKit iOS Documentation Source: https://developer.repebble.com/docs/pebblekit-ios/ > **Important: PebbleKit iOS has been discontinued.** Apple has made it increasingly difficult for third-party smartwatches to provide a competitive experience on iOS. Over the years, Apple has systematically restricted capabilities that were essential to Pebble's functionality, including the ability to reply to notifications, send messages, enable inter-app communication, and properly function when the companion app is in the background. These restrictions have made it nearly impossible to build a smartwatch experience on iOS that matches the capabilities available on Android. We encourage developers to focus on Android development where Pebble can deliver its full potential without artificial platform limitations. Learn more about Apple's restrictions and their impact on third-party wearables in [this blog post](https://ericmigi.com/blog/apple-restricts-pebble-from-being-awesome-with-iphones). ### Class References ##### [PBBitmap](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md) ##### [PBDataLoggingService](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService.md) ##### [PBDataLoggingSessionMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md) ##### [PBFirmwareMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md) ##### [PBFirmwareVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md) ##### [PBPebbleCentral](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md) ##### [PBPebbleKitLogging](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging.md) ##### [PBResourceMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBResourceMetadata.md) ##### [PBSemanticVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md) ##### [PBSportsUpdate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md) ##### [PBVersionInfo](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md) ##### [PBWatch](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md) ### Protocol References ##### [PBDataLoggingServiceDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md) ##### [PBPebbleCentralDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate.md) ##### [PBWatchDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md) ### Constant References ##### [FirmwareMetadataPlatform](https://developer.repebble.com/docs/pebblekit-ios/Constants/FirmwareMetadataPlatform.md) ##### [GolfAppCommand](https://developer.repebble.com/docs/pebblekit-ios/Constants/GolfAppCommand.md) ##### [PBDataLoggingType](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBDataLoggingType.md) ##### [PBErrorCode](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBErrorCode.md) ##### [PBPebbleKitLogFlag](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogFlag.md) ##### [PBPebbleKitLogLevel](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogLevel.md) ##### [PBRemoteProtocolCapabilitiesFlags](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBRemoteProtocolCapabilitiesFlags.md) ##### [PBUnitSystem](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBUnitSystem.md) ##### [SportsAppActivityState](https://developer.repebble.com/docs/pebblekit-ios/Constants/SportsAppActivityState.md) ### Category References ##### [NSData(Pebble)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSData%2BPebble/) ##### [NSDictionary(Pebble)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSDictionary%2BPebble/) ##### [NSError(Pebble)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSError%2BPebble/) ##### [NSNumber(PBStandardIntegerExtensions)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/) --- # PebbleKit iOS Hierarchy Source: https://developer.repebble.com/docs/pebblekit-ios/hierarchy/ ## Class Hierarchy - NSObject - [PBBitmap](Classes/PBBitmap/) - [PBDataLoggingService](Classes/PBDataLoggingService/) - [PBDataLoggingSessionMetadata](Classes/PBDataLoggingSessionMetadata/) - [PBFirmwareMetadata](Classes/PBFirmwareMetadata/) - [PBPebbleCentral](Classes/PBPebbleCentral/) - [PBPebbleKitLogging](Classes/PBPebbleKitLogging/) - [PBResourceMetadata](Classes/PBResourceMetadata/) - [PBSemanticVersion](Classes/PBSemanticVersion/) - [PBFirmwareVersion](Classes/PBFirmwareVersion/) - [PBSportsUpdate](Classes/PBSportsUpdate/) - [PBVersionInfo](Classes/PBVersionInfo/) - [PBWatch](Classes/PBWatch/) ## Protocol References - [PBDataLoggingServiceDelegate](Protocols/PBDataLoggingServiceDelegate/) - [PBPebbleCentralDelegate](Protocols/PBPebbleCentralDelegate/) - [PBWatchDelegate](Protocols/PBWatchDelegate/) ## Constant References - [FirmwareMetadataPlatform](Constants/FirmwareMetadataPlatform/) - [GolfAppCommand](Constants/GolfAppCommand/) - [PBDataLoggingType](Constants/PBDataLoggingType/) - [PBErrorCode](Constants/PBErrorCode/) - [PBPebbleKitLogFlag](Constants/PBPebbleKitLogFlag/) - [PBPebbleKitLogLevel](Constants/PBPebbleKitLogLevel/) - [PBRemoteProtocolCapabilitiesFlags](Constants/PBRemoteProtocolCapabilitiesFlags/) - [PBUnitSystem](Constants/PBUnitSystem/) - [SportsAppActivityState](Constants/SportsAppActivityState/) ## Category References - [NSData(Pebble)](Categories/NSData%2BPebble/) - [NSDictionary(Pebble)](Categories/NSDictionary%2BPebble/) - [NSError(Pebble)](Categories/NSError%2BPebble/) - [NSNumber(PBStandardIntegerExtensions)](Categories/NSNumber%2BPBStandardIntegerExtensions/) --- # PebbleKit iOS Reference Source: https://developer.repebble.com/docs/pebblekit-ios/index/ ## Class References - [PBBitmap](Classes/PBBitmap/) - [PBDataLoggingService](Classes/PBDataLoggingService/) - [PBDataLoggingSessionMetadata](Classes/PBDataLoggingSessionMetadata/) - [PBFirmwareMetadata](Classes/PBFirmwareMetadata/) - [PBFirmwareVersion](Classes/PBFirmwareVersion/) - [PBPebbleCentral](Classes/PBPebbleCentral/) - [PBPebbleKitLogging](Classes/PBPebbleKitLogging/) - [PBResourceMetadata](Classes/PBResourceMetadata/) - [PBSemanticVersion](Classes/PBSemanticVersion/) - [PBSportsUpdate](Classes/PBSportsUpdate/) - [PBVersionInfo](Classes/PBVersionInfo/) - [PBWatch](Classes/PBWatch/) ## Protocol References - [PBDataLoggingServiceDelegate](Protocols/PBDataLoggingServiceDelegate/) - [PBPebbleCentralDelegate](Protocols/PBPebbleCentralDelegate/) - [PBWatchDelegate](Protocols/PBWatchDelegate/) ## Constant References - [FirmwareMetadataPlatform](Constants/FirmwareMetadataPlatform/) - [GolfAppCommand](Constants/GolfAppCommand/) - [PBDataLoggingType](Constants/PBDataLoggingType/) - [PBErrorCode](Constants/PBErrorCode/) - [PBPebbleKitLogFlag](Constants/PBPebbleKitLogFlag/) - [PBPebbleKitLogLevel](Constants/PBPebbleKitLogLevel/) - [PBRemoteProtocolCapabilitiesFlags](Constants/PBRemoteProtocolCapabilitiesFlags/) - [PBUnitSystem](Constants/PBUnitSystem/) - [SportsAppActivityState](Constants/SportsAppActivityState/) ## Category References - [NSData(Pebble)](Categories/NSData%2BPebble/) - [NSDictionary(Pebble)](Categories/NSDictionary%2BPebble/) - [NSError(Pebble)](Categories/NSError%2BPebble/) - [NSNumber(PBStandardIntegerExtensions)](Categories/NSNumber%2BPBStandardIntegerExtensions/) --- # SportsAppActivityState Constants Reference Source: https://developer.repebble.com/docs/pebblekit-ios/Constants/SportsAppActivityState/ | Declared in | PBWatch+Sports.h | | References | [-[PBWatch sportsAppAddReceiveUpdateHandler:]](Classes/PBWatch/#//api/name/sportsAppAddReceiveUpdateHandler:) | ### SportsAppActivityState The state of the Sports activity. #### Definition `typedef NS_ENUM(uint8_t, SportsAppActivityState ) {    SportsAppActivityStateInit = 0 x00,    SportsAppActivityStateRunning = 0 x01,    SportsAppActivityStatePaused = 0 x02,    SportsAppActivityStateEnd = 0 x03, };` #### Constants `SportsAppActivityStateInit` The state of the Sports activity. Declared In `PBWatch+Sports.h`. `SportsAppActivityStateRunning` The state of the Sports activity. Declared In `PBWatch+Sports.h`. `SportsAppActivityStatePaused` The state of the Sports activity. Declared In `PBWatch+Sports.h`. `SportsAppActivityStateEnd` The state of the Sports activity. Declared In `PBWatch+Sports.h`. #### See Also - `[PBWatch sportsAppAddReceiveUpdateHandler:]` #### Declared In `PBWatch+Sports.h` --- # SDK Documentation Source: https://developer.repebble.com/docs/ Welcome to the API Documentation section of the Pebble Developers site! Here you will find complete listings of all the classes, objects, methods and functions available across all the parts of the Pebble API. ## Pebble Smartwatch APIs Pebble's smartwatch APIs provide developers with a means of developing applications that run natively on Pebble smartwatches. ### [Pebble C API](https://developer.repebble.com/docs/c.md) The Pebble C API, used for creating native **watchapps and watchfaces** in C. The Pebble C API can be used in combination with _any_ of the PebbleKit APIs listed below to extend the application's functionality. ### [Moddable API (Alloy)](https://www.moddable.com/documentation/readme) Alloy is a modern JavaScript framework for building native Pebble **watchapps and watchfaces** , based on the Moddable SDK. It provides a rich set of APIs for creating complex user interfaces, accessing sensors, and communicating with the phone. Alloy is actively developed with new features and improvements. ## PebbleKit APIs The PebbleKit APIs provides developers with a means to extend their application's functionality by communicating with an application on the mobile device it is paired to. ### [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) PebbleKit JS enables developers to extend their Pebble projects by adding a JavaScript component that is managed by the Pebble mobile app. PebbleKit JS is capable of bidirectional communication with with application running on the Pebble smartwatch ### [PebbleKit iOS](https://developer.repebble.com/docs/pebblekit-ios.md) PebbleKit iOS is an Objective-C library that enables developers to create companion apps for iOS devices that are capable for bi-directional communication with their Pebble API projects. ### [PebbleKit Android](https://github.com/pebble-dev/PebbleKitAndroid2) PebbleKit Android is a Java library that enables developers to create companion apps for Android devices that are capable for bi-directional communication with their Pebble API projects. --- # symbols Source: https://developer.repebble.com/docs/symbols/ | c | [Foundation](https://developer.repebble.com/docs/c/Foundation.md) | | The core of the Pebble SDK. | | c | [Alloy](https://developer.repebble.com/docs/c/Foundation/Alloy.md) | | | | c | [kModdableCreationFlagLogInstrumentation](https://developer.repebble.com/docs/c/Foundation/Alloy.md#kModdableCreationFlagLogInstrumentation) | | Flag to enable XS instrumentation logging over Bluetooth. When set, the Moddable XS engine will log instrumentation data (e.g. memory usage, slot/chunk/stack statistics) via app\_log. Logging is only active when a Bluetooth log listener is connected; otherwise this flag has no effect. | | c | [kModdableCreationFlagDebug](https://developer.repebble.com/docs/c/Foundation/Alloy.md#kModdableCreationFlagDebug) | | Flag to enable XS debugging using xsbug. Should be used in combination with pebble build -debug. | | c | [moddable\_createMachine](https://developer.repebble.com/docs/c/Foundation/Alloy.md#moddable_createMachine) | | Create and start a Moddable XS virtual machine for an Alloy app. | | c | [ModdableCreationRecord](https://developer.repebble.com/docs/c/Foundation/Alloy.md#ModdableCreationRecord) | | Configuration record for creating a Moddable XS virtual machine. Used with [moddable\_createMachine()](https://developer.repebble.com/docs/c/Foundation/Alloy.md#moddable_createMachine) to customize the JS runtime. Set recordSize to sizeof(ModdableCreationRecord) for version compatibility. | | c | [App](https://developer.repebble.com/docs/c/Foundation/App.md) | | App entry point and event loop. | | c | [app\_event\_loop](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop) | | The event loop for C apps, to be used in app's main(). Will block until the app is ready to exit. | | c | [App Communication](https://developer.repebble.com/docs/c/Foundation/App_Communication.md) | | API for interacting with the Pebble communication subsystem. | | c | [SniffInterval](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#SniffInterval) | | Intervals during which the Bluetooth module may enter a low power mode. The sniff interval defines the period during which the Bluetooth module may not exchange (ACL) packets. The longer the sniff interval, the more time the Bluetooth module may spend in a low power mode. It may be necessary to reduce the sniff interval if an app requires reduced latency when sending messages. | | c | [SNIFF\_INTERVAL\_NORMAL](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#SNIFF_INTERVAL_NORMAL) | | Set the sniff interval to normal (power-saving) mode. | | c | [SNIFF\_INTERVAL\_REDUCED](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#SNIFF_INTERVAL_REDUCED) | | Reduce the sniff interval to increase the responsiveness of the radio at the expense of increasing Bluetooth energy consumption by a multiple of 2-5 (very significant) | | c | [app\_comm\_set\_sniff\_interval](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#app_comm_set_sniff_interval) | | Set the Bluetooth module's sniff interval. The sniff interval will be restored to normal by the OS after the app's de-init handler is called. Set the sniff interval to normal whenever possible. | | c | [app\_comm\_get\_sniff\_interval](https://developer.repebble.com/docs/c/Foundation/App_Communication.md#app_comm_get_sniff_interval) | | Get the Bluetooth module's sniff interval. | | c | [App Glance](https://developer.repebble.com/docs/c/Foundation/App_Glance.md) | | API for the application to modify its "glance" i.e. app menu subtitle. | | c | [PublishedId](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#PublishedId) | | The ID of a published app resource defined within the publishedMedia section of package.json. | | c | [APP\_GLANCE\_SLICE\_NO\_EXPIRATION](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_NO_EXPIRATION) | | Can be used for the expiration\_time of an [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) so that the slice never expires. | | c | [APP\_GLANCE\_SLICE\_DEFAULT\_ICON](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_SLICE_DEFAULT_ICON) | | Can be used for the icon of an [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) so that the slice displays the app's default icon. | | c | [AppGlanceResult](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceResult) | | Bitfield enum describing the result of trying to add an [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) to an app's glance. | | c | [APP\_GLANCE\_RESULT\_SUCCESS](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_RESULT_SUCCESS) | | The slice was successfully added to the app's glance. | | c | [APP\_GLANCE\_RESULT\_INVALID\_TEMPLATE\_STRING](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_RESULT_INVALID_TEMPLATE_STRING) | | The subtitle\_template\_string provided in the slice was invalid. | | c | [APP\_GLANCE\_RESULT\_TEMPLATE\_STRING\_TOO\_LONG](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_RESULT_TEMPLATE_STRING_TOO_LONG) | | The subtitle\_template\_string provided in the slice was longer than 150 bytes. | | c | [APP\_GLANCE\_RESULT\_INVALID\_ICON](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_RESULT_INVALID_ICON) | | The icon provided in the slice was invalid. | | c | [APP\_GLANCE\_RESULT\_SLICE\_CAPACITY\_EXCEEDED](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_RESULT_SLICE_CAPACITY_EXCEEDED) | | The provided slice would exceed the app glance's slice capacity. | | c | [APP\_GLANCE\_RESULT\_EXPIRES\_IN\_THE\_PAST](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_RESULT_EXPIRES_IN_THE_PAST) | | The expiration\_time provided in the slice expires in the past. | | c | [APP\_GLANCE\_RESULT\_INVALID\_SESSION](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#APP_GLANCE_RESULT_INVALID_SESSION) | | The AppGlanceReloadSession provided was invalid. | | c | [AppGlanceReloadSession](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadSession) | | | | c | [app\_glance\_add\_slice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_add_slice) | | Add a slice to the app's glance. This function will only succeed if called with a valid AppGlanceReloadSession that is provided in an [AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback). | | c | [AppGlanceReloadCallback](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceReloadCallback) | | User-provided callback for reloading the slices in the app's glance. | | c | [app\_glance\_reload](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#app_glance_reload) | | Clear any existing slices in the app's glance and trigger a reload via the provided callback. | | c | [AppGlanceSlice](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice) | | An app's glance can change over time as defined by zero or more app glance slices that each describe the state of the app glance at a particular point in time. Slices are displayed in the order they are added, and they are removed at the specified expiration time. | | c | [AppGlanceSliceLayout](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSliceLayout) | | Describes how the slice should be visualized in the app's glance in the launcher. | | c | [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md) | | | | c | [AppMessageResult](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageResult) | | AppMessage result codes. | | c | [APP\_MSG\_OK](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_OK) | | (0) All good, operation was successful. | | c | [APP\_MSG\_SEND\_TIMEOUT](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_SEND_TIMEOUT) | | (2) The other end did not confirm receiving the sent data with an (n)ack in time. | | c | [APP\_MSG\_SEND\_REJECTED](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_SEND_REJECTED) | | (4) The other end rejected the sent data, with a "nack" reply. | | c | [APP\_MSG\_NOT\_CONNECTED](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_NOT_CONNECTED) | | (8) The other end was not connected. | | c | [APP\_MSG\_APP\_NOT\_RUNNING](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_APP_NOT_RUNNING) | | (16) The local application was not running. | | c | [APP\_MSG\_INVALID\_ARGS](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_INVALID_ARGS) | | (32) The function was called with invalid arguments. | | c | [APP\_MSG\_BUSY](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_BUSY) | | (64) There are pending (in or outbound) messages that need to be processed first before new ones can be received or sent. | | c | [APP\_MSG\_BUFFER\_OVERFLOW](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_BUFFER_OVERFLOW) | | (128) The buffer was too small to contain the incoming message. | | c | [APP\_MSG\_ALREADY\_RELEASED](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_ALREADY_RELEASED) | | (512) The resource had already been released. | | c | [APP\_MSG\_CALLBACK\_ALREADY\_REGISTERED](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_CALLBACK_ALREADY_REGISTERED) | | (1024) The callback was already registered. | | c | [APP\_MSG\_CALLBACK\_NOT\_REGISTERED](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_CALLBACK_NOT_REGISTERED) | | (2048) The callback could not be deregistered, because it had not been registered before. | | c | [APP\_MSG\_OUT\_OF\_MEMORY](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_OUT_OF_MEMORY) | | (4096) The system did not have sufficient application memory to perform the requested operation. | | c | [APP\_MSG\_CLOSED](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_CLOSED) | | (8192) App message was closed. | | c | [APP\_MSG\_INTERNAL\_ERROR](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_INTERNAL_ERROR) | | (16384) An internal OS error prevented AppMessage from completing an operation. | | c | [APP\_MSG\_INVALID\_STATE](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MSG_INVALID_STATE) | | (32768) The function was called while App Message was not in the appropriate state. | | c | [app\_message\_open](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_open) | | Open AppMessage to transfers. | | c | [app\_message\_deregister\_callbacks](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_deregister_callbacks) | | Deregisters all callbacks and their context. | | c | [AppMessageInboxReceived](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxReceived) | | Called after an incoming message is received. | | c | [AppMessageInboxDropped](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageInboxDropped) | | Called after an incoming message is dropped. | | c | [AppMessageOutboxSent](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxSent) | | Called after an outbound message has been sent and the reply has been received. | | c | [AppMessageOutboxFailed](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#AppMessageOutboxFailed) | | Called after an outbound message has not been sent successfully. | | c | [app\_message\_get\_context](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_get_context) | | Gets the context that will be passed to all AppMessage callbacks. | | c | [app\_message\_set\_context](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_set_context) | | Sets the context that will be passed to all AppMessage callbacks. | | c | [app\_message\_register\_inbox\_received](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_inbox_received) | | Registers a function that will be called after any Inbox message is received successfully. | | c | [app\_message\_register\_inbox\_dropped](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_inbox_dropped) | | Registers a function that will be called after any Inbox message is received but dropped by the system. | | c | [app\_message\_register\_outbox\_sent](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_outbox_sent) | | Registers a function that will be called after any Outbox message is sent and an ACK reply occurs in a timely fashion. | | c | [app\_message\_register\_outbox\_failed](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_register_outbox_failed) | | Registers a function that will be called after any Outbox message is not sent with a timely ACK reply. The call to [app\_message\_outbox\_send()](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_send) must have succeeded. | | c | [app\_message\_inbox\_size\_maximum](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_inbox_size_maximum) | | Programatically determine the inbox size maximum in the current configuration. | | c | [app\_message\_outbox\_size\_maximum](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_size_maximum) | | Programatically determine the outbox size maximum in the current configuration. | | c | [app\_message\_outbox\_begin](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_begin) | | Begin writing to the Outbox's Dictionary buffer. | | c | [app\_message\_outbox\_send](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_send) | | Sends the outbound dictionary. | | c | [APP\_MESSAGE\_INBOX\_SIZE\_MINIMUM](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MESSAGE_INBOX_SIZE_MINIMUM) | | As long as the firmware maintains its current major version, inboxes of this size or smaller will be allowed. | | c | [APP\_MESSAGE\_OUTBOX\_SIZE\_MINIMUM](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#APP_MESSAGE_OUTBOX_SIZE_MINIMUM) | | As long as the firmware maintains its current major version, outboxes of this size or smaller will be allowed. | | c | [AppSync](https://developer.repebble.com/docs/c/Foundation/AppSync.md) | | UI synchronization layer for AppMessage | | c | [AppSyncTupleChangedCallback](https://developer.repebble.com/docs/c/Foundation/AppSync.md#AppSyncTupleChangedCallback) | | Called whenever a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) changes. This does not necessarily mean the value in the [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) has changed. When the internal "current" dictionary gets updated, existing Tuples might get shuffled around in the backing buffer, even though the values stay the same. In this callback, the client code gets the chance to remove the old reference and start using the new one. In this callback, your application MUST clean up any references to the `old_tuple` of a PREVIOUS call to this callback (and replace it with the `new_tuple` that is passed in with the current call). | | c | [AppSyncErrorCallback](https://developer.repebble.com/docs/c/Foundation/AppSync.md#AppSyncErrorCallback) | | Called whenever there was an error. | | c | [app\_sync\_init](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_init) | | Initialized an AppSync system with specific buffer size and initial keys and values. The `callback.value_changed` callback will be called **asynchronously** with the initial keys and values, as to avoid duplicating code to update your app's UI. | | c | [app\_sync\_deinit](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_deinit) | | Cleans up an AppSync system. It frees the buffer allocated by an [app\_sync\_init()](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_init) call and deregisters itself from the [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md) subsystem. | | c | [app\_sync\_set](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_set) | | Updates key/value pairs using an array of Tuplets. | | c | [app\_sync\_get](https://developer.repebble.com/docs/c/Foundation/AppSync.md#app_sync_get) | | Finds and gets a tuple in the "current" dictionary. | | c | [AppSync](https://developer.repebble.com/docs/c/Foundation/AppSync.md#AppSync) | | | | c | [AppWorker](https://developer.repebble.com/docs/c/Foundation/AppWorker.md) | | Runs in the background, and can communicate with the foreground app. | | c | [AppWorkerResult](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerResult) | | Possible error codes from app\_worker\_launch, app\_worker\_kill. | | c | [APP\_WORKER\_RESULT\_SUCCESS](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_SUCCESS) | | Success. | | c | [APP\_WORKER\_RESULT\_NO\_WORKER](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_NO_WORKER) | | No worker found for the current app. | | c | [APP\_WORKER\_RESULT\_DIFFERENT\_APP](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_DIFFERENT_APP) | | A worker for a different app is already running. | | c | [APP\_WORKER\_RESULT\_NOT\_RUNNING](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_NOT_RUNNING) | | The worker is not running. | | c | [APP\_WORKER\_RESULT\_ALREADY\_RUNNING](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_ALREADY_RUNNING) | | The worker is already running. | | c | [APP\_WORKER\_RESULT\_ASKING\_CONFIRMATION](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#APP_WORKER_RESULT_ASKING_CONFIRMATION) | | The user will be asked for confirmation. | | c | [app\_worker\_is\_running](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_is_running) | | Determine if the worker for the current app is running. | | c | [app\_worker\_launch](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_launch) | | Launch the worker for the current app. Note that this is an asynchronous operation, a result code of APP\_WORKER\_RESULT\_SUCCESS merely means that the request was successfully queued up. | | c | [app\_worker\_kill](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_kill) | | Kill the worker for the current app. Note that this is an asynchronous operation, a result code of APP\_WORKER\_RESULT\_SUCCESS merely means that the request was successfully queued up. | | c | [AppWorkerMessageHandler](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerMessageHandler) | | Callback type for worker messages. Messages can be sent from worker to app or vice versa. | | c | [app\_worker\_message\_subscribe](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_message_subscribe) | | Subscribe to worker messages. Once subscribed, the handler gets called on every message emitted by the other task (either worker or app). | | c | [app\_worker\_message\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_message_unsubscribe) | | Unsubscribe from worker messages. Once unsubscribed, the previously registered handler will no longer be called. | | c | [app\_worker\_send\_message](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#app_worker_send_message) | | Send a message to the other task (either worker or app). | | c | [AppWorkerMessage](https://developer.repebble.com/docs/c/Foundation/AppWorker.md#AppWorkerMessage) | | Generic structure of a worker message that can be sent between an app and its worker. | | c | [DataLogging](https://developer.repebble.com/docs/c/Foundation/DataLogging.md) | | Enables logging data asynchronously to a mobile app | | c | [DataLoggingSessionRef](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingSessionRef) | | | | c | [DataLoggingItemType](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingItemType) | | The different types of session data that Pebble supports. This type describes the type of a singular item in the data session. Every item in a given session is the same type and size. | | c | [DATA\_LOGGING\_BYTE\_ARRAY](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_BYTE_ARRAY) | | Array of bytes. Remember that this is the type of a single item in the logging session, so using this type means you'll be logging multiple byte arrays (each a fixed length described by item\_length) for the duration of the session. | | c | [DATA\_LOGGING\_UINT](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_UINT) | | Unsigned integer. This may be a 1, 2, or 4 byte integer depending on the item\_length parameter. | | c | [DATA\_LOGGING\_INT](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_INT) | | Signed integer. This may be a 1, 2, or 4 byte integer depending on the item\_length parameter. | | c | [DataLoggingResult](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DataLoggingResult) | | Enumerated values describing the possible outcomes of data logging operations. | | c | [DATA\_LOGGING\_SUCCESS](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_SUCCESS) | | Successful operation. | | c | [DATA\_LOGGING\_BUSY](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_BUSY) | | Someone else is writing to this logging session. | | c | [DATA\_LOGGING\_FULL](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_FULL) | | No more space to save data. | | c | [DATA\_LOGGING\_NOT\_FOUND](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_NOT_FOUND) | | The logging session does not exist. | | c | [DATA\_LOGGING\_CLOSED](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_CLOSED) | | The logging session was made inactive. | | c | [DATA\_LOGGING\_INVALID\_PARAMS](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_INVALID_PARAMS) | | An invalid parameter was passed to one of the functions. | | c | [DATA\_LOGGING\_INTERNAL\_ERR](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#DATA_LOGGING_INTERNAL_ERR) | | An internal error occurred. | | c | [data\_logging\_create](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#data_logging_create) | | Create a new data logging session. | | c | [data\_logging\_finish](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#data_logging_finish) | | Finish up a data logging\_session. Logging data is kept until it has successfully been transferred over to the phone, but no data may be added to the session after this function is called. | | c | [data\_logging\_log](https://developer.repebble.com/docs/c/Foundation/DataLogging.md#data_logging_log) | | Add data to the data logging session. If a phone is available, the data is sent directly to the phone. Otherwise, it is saved to the watch storage until the watch is connected to a phone. | | c | [DataStructures](https://developer.repebble.com/docs/c/Foundation/DataStructures/) | | | | c | [UUID](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md) | | | | c | [UUID\_SIZE](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#UUID_SIZE) | | | | c | [UuidMake](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#UuidMake) | | Make a Uuid object from sixteen bytes. | | c | [UuidMakeFromBEBytes](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#UuidMakeFromBEBytes) | | Creates a Uuid from an array of bytes with 16 bytes in Big Endian order. | | c | [UuidMakeFromLEBytes](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#UuidMakeFromLEBytes) | | Creates a Uuid from an array of bytes with 16 bytes in Little Endian order. | | c | [uuid\_equal](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#uuid_equal) | | Compares two UUIDs. | | c | [uuid\_to\_string](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#uuid_to_string) | | Writes UUID in a string form into buffer that looks like the following... {12345678-1234-5678-1234-567812345678} or {NULL UUID} if NULL was passed. | | c | [UUID\_STRING\_BUFFER\_LENGTH](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#UUID_STRING_BUFFER_LENGTH) | | The minimum required length of a string used to hold a uuid (including null). | | c | [Uuid](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md#Uuid) | | | | c | [Dictation](https://developer.repebble.com/docs/c/Foundation/Dictation.md) | | | | c | [DictationSession](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession) | | | | c | [DictationSessionStatus](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatus) | | | | c | [DictationSessionStatusSuccess](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusSuccess) | | Transcription successful, with a valid result. | | c | [DictationSessionStatusFailureTranscriptionRejected](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureTranscriptionRejected) | | User rejected transcription and exited UI. | | c | [DictationSessionStatusFailureTranscriptionRejectedWithError](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureTranscriptionRejectedWithError) | | User exited UI after transcription error. | | c | [DictationSessionStatusFailureSystemAborted](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureSystemAborted) | | Too many errors occurred during transcription and the UI exited. | | c | [DictationSessionStatusFailureNoSpeechDetected](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureNoSpeechDetected) | | No speech was detected and UI exited. | | c | [DictationSessionStatusFailureConnectivityError](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureConnectivityError) | | No BT or internet connection. | | c | [DictationSessionStatusFailureDisabled](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureDisabled) | | Voice transcription disabled for this user. | | c | [DictationSessionStatusFailureInternalError](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureInternalError) | | Voice transcription failed due to internal error. | | c | [DictationSessionStatusFailureRecognizerError](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusFailureRecognizerError) | | Cloud recognizer failed to transcribe speech (only possible if error dialogs disabled) | | c | [DictationSessionStatusCallback](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback) | | Dictation status callback. Indicates success or failure of the dictation session and, if successful, passes the transcribed string to the user of the dictation session. The transcribed string will be freed after this call returns, so the string should be copied if it needs to be retained afterwards. | | c | [dictation\_session\_create](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_create) | | Create a dictation session. The session object can be used more than once to get a transcription. When a transcription is received a buffer will be allocated to store the text in with a maximum size specified by `buffer_size`. When a transcription is accepted by the user or a failure of some sort occurs, the callback specified will be called with the status and the transcription if one was accepted. | | c | [dictation\_session\_destroy](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_destroy) | | Destroy the dictation session and free its memory. Will terminate a session in progress. | | c | [dictation\_session\_start](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_start) | | Start the dictation session. The dictation UI will be shown. When the user accepts a transcription or exits the UI, or, when the confirmation dialog is disabled and a status is received, the status callback will be called. Can only be called when no session is in progress. The session can be restarted multiple times after the UI is exited or the session is stopped. | | c | [dictation\_session\_stop](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_stop) | | Stop the current dictation session. The UI will be hidden and no status callbacks will be received after the session is stopped. | | c | [dictation\_session\_enable\_confirmation](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_enable_confirmation) | | Enable or disable user confirmation of transcribed text, which allows the user to accept or reject (and restart) the transcription. Must be called before the session is started. | | c | [dictation\_session\_enable\_error\_dialogs](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_enable_error_dialogs) | | Enable or disable error dialogs when transcription fails. Must be called before the session is started. Disabling error dialogs will also disable automatic retries if transcription fails. | | c | [PBL\_IF\_MICROPHONE\_ELSE](https://developer.repebble.com/docs/c/Foundation/Dictation.md#PBL_IF_MICROPHONE_ELSE) | | Convenience macro to switch between two expressions depending on mic support. On platforms with a mic the first expression will be chosen, the second otherwise. | | c | [Dictionary](https://developer.repebble.com/docs/c/Foundation/Dictionary.md) | | Data serialization utilities | | c | [DictionaryResult](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryResult) | | Return values for dictionary write/conversion functions. | | c | [DICT\_OK](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DICT_OK) | | The operation returned successfully. | | c | [DICT\_NOT\_ENOUGH\_STORAGE](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DICT_NOT_ENOUGH_STORAGE) | | There was not enough backing storage to complete the operation. | | c | [DICT\_INVALID\_ARGS](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DICT_INVALID_ARGS) | | One or more arguments were invalid or uninitialized. | | c | [DICT\_INTERNAL\_INCONSISTENCY](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DICT_INTERNAL_INCONSISTENCY) | | The lengths and/or count of the dictionary its tuples are inconsistent. | | c | [DICT\_MALLOC\_FAILED](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DICT_MALLOC_FAILED) | | A requested operation required additional memory to be allocated, but the allocation failed, likely due to insufficient remaining heap memory. | | c | [TupleType](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TupleType) | | Values representing the type of data that the `value` field of a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) contains. | | c | [TUPLE\_BYTE\_ARRAY](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TUPLE_BYTE_ARRAY) | | The value is an array of bytes. | | c | [TUPLE\_CSTRING](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TUPLE_CSTRING) | | The value is a zero-terminated, UTF-8 C-string. | | c | [TUPLE\_UINT](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TUPLE_UINT) | | The value is an unsigned integer. The tuple's `.length` field is used to determine the size of the integer (1, 2, or 4 bytes). | | c | [TUPLE\_INT](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TUPLE_INT) | | The value is a signed integer. The tuple's `.length` field is used to determine the size of the integer (1, 2, or 4 bytes). | | c | [Dictionary](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Dictionary) | | | | c | [dict\_calc\_buffer\_size](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_calc_buffer_size) | | Calculates the number of bytes that a dictionary will occupy, given one or more value lengths that need to be stored in the dictionary. | | c | [dict\_size](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_size) | | Calculates the size of data that has been written to the dictionary. AKA, the "dictionary size". Note that this is most likely different than the size of the backing storage/backing buffer. | | c | [dict\_write\_begin](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_begin) | | Initializes the dictionary iterator with a given buffer and size, resets and empties it, in preparation of writing key/value tuples. | | c | [dict\_write\_data](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_data) | | Adds a key with a byte array value pair to the dictionary. | | c | [dict\_write\_cstring](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_cstring) | | Adds a key with a C string value pair to the dictionary. | | c | [dict\_write\_int](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_int) | | Adds a key with an integer value pair to the dictionary. | | c | [dict\_write\_uint8](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_uint8) | | Adds a key with an unsigned, 8-bit integer value pair to the dictionary. | | c | [dict\_write\_uint16](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_uint16) | | | | c | [dict\_write\_uint32](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_uint32) | | | | c | [dict\_write\_int8](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_int8) | | | | c | [dict\_write\_int16](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_int16) | | | | c | [dict\_write\_int32](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_int32) | | | | c | [dict\_write\_end](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_end) | | End a series of writing operations to a dictionary. This must be called before reading back from the dictionary. | | c | [dict\_read\_begin\_from\_buffer](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_begin_from_buffer) | | Initializes the dictionary iterator with a given buffer and size, in preparation of reading key/value tuples. | | c | [dict\_read\_next](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_next) | | Progresses the iterator to the next key/value pair. | | c | [dict\_read\_first](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_first) | | Resets the iterator back to the same state as a call to [dict\_read\_begin\_from\_buffer()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_begin_from_buffer) would do. | | c | [TupletBytes](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TupletBytes) | | Macro to create a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) with a byte array value. | | c | [TupletCString](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TupletCString) | | Macro to create a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) with a c-string value. | | c | [TupletInteger](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#TupletInteger) | | Macro to create a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) with an integer value. | | c | [DictionarySerializeCallback](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionarySerializeCallback) | | Callback for [dict\_serialize\_tuplets()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_serialize_tuplets) utility. | | c | [dict\_serialize\_tuplets](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_serialize_tuplets) | | Utility function that takes a list of Tuplets from which a dictionary will be serialized, ready to transmit or store. | | c | [dict\_serialize\_tuplets\_to\_buffer](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_serialize_tuplets_to_buffer) | | Utility function that takes an array of Tuplets and serializes them into a dictionary with a given buffer and size. | | c | [dict\_serialize\_tuplets\_to\_buffer\_with\_iter](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_serialize_tuplets_to_buffer_with_iter) | | Serializes an array of Tuplets into a dictionary with a given buffer and size. | | c | [dict\_write\_tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_tuplet) | | Serializes a [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) and writes the resulting [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) into a dictionary. | | c | [dict\_calc\_buffer\_size\_from\_tuplets](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_calc_buffer_size_from_tuplets) | | Calculates the number of bytes that a dictionary will occupy, given one or more Tuplets that need to be stored in the dictionary. | | c | [DictionaryKeyUpdatedCallback](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryKeyUpdatedCallback) | | Type of the callback used in [dict\_merge()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) | | c | [dict\_merge](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_merge) | | Merges entries from another "source" dictionary into a "destination" dictionary. All Tuples from the source are written into the destination dictionary, while updating the exsting Tuples with matching keys. | | c | [dict\_find](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_find) | | Tries to find a [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) with specified key in a dictionary. | | c | [DictionaryIterator](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#DictionaryIterator) | | An iterator can be used to iterate over the key/value tuples in an existing dictionary, using [dict\_read\_begin\_from\_buffer()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_begin_from_buffer), [dict\_read\_first()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_first) and [dict\_read\_next()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_read_next). An iterator can also be used to append key/value tuples to a dictionary, for example using [dict\_write\_data()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_data) or [dict\_write\_cstring()](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#dict_write_cstring). | | c | [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple) | | Data structure for one serialized key/value tuple. | | c | [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) | | Non-serialized, template data structure for a key/value pair. For strings and byte arrays, it only has a pointer to the actual data. For integers, it provides storage for integers up to 32-bits wide. The [Tuplet](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuplet) data structure is useful when creating dictionaries from values that are already stored in arbitrary buffers. See also [Tuple](https://developer.repebble.com/docs/c/Foundation/Dictionary.md#Tuple), with is the header of a serialized key/value pair. | | c | [Event Service](https://developer.repebble.com/docs/c/Foundation/Event_Service.md) | | APIs to handle event services. | | c | [AccelerometerService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md) | | | | c | [AccelAxisType](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelAxisType) | | Enumerated values defining the three accelerometer axes. | | c | [ACCEL\_AXIS\_X](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_AXIS_X) | | Accelerometer's X axis. The positive direction along the X axis goes toward the right of the watch. | | c | [ACCEL\_AXIS\_Y](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_AXIS_Y) | | Accelerometer's Y axis. The positive direction along the Y axis goes toward the top of the watch. | | c | [ACCEL\_AXIS\_Z](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_AXIS_Z) | | Accelerometer's Z axis. The positive direction along the Z axis goes vertically out of the watchface. | | c | [AccelDataHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelDataHandler) | | Callback type for accelerometer data events. | | c | [AccelRawDataHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelRawDataHandler) | | Callback type for accelerometer raw data events. | | c | [AccelTapHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelTapHandler) | | Callback type for accelerometer tap events. | | c | [AccelSamplingRate](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelSamplingRate) | | Valid accelerometer sampling rates, in Hz. | | c | [ACCEL\_SAMPLING\_10HZ](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_SAMPLING_10HZ) | | 10 HZ sampling rate | | c | [ACCEL\_SAMPLING\_25HZ](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_SAMPLING_25HZ) | | 25 HZ sampling rate [Default] | | c | [ACCEL\_SAMPLING\_50HZ](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_SAMPLING_50HZ) | | 50 HZ sampling rate | | c | [ACCEL\_SAMPLING\_100HZ](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#ACCEL_SAMPLING_100HZ) | | 100 HZ sampling rate | | c | [accel\_service\_peek](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_service_peek) | | Peek at the last recorded reading. | | c | [accel\_service\_set\_sampling\_rate](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_service_set_sampling_rate) | | Change the accelerometer sampling rate. | | c | [accel\_service\_set\_samples\_per\_update](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_service_set_samples_per_update) | | Change the number of samples buffered between each accelerometer data event. | | c | [accel\_data\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_data_service_subscribe) | | Subscribe to the accelerometer data event service. Once subscribed, the handler gets called every time there are new accelerometer samples available. | | c | [accel\_data\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_data_service_unsubscribe) | | Unsubscribe from the accelerometer data event service. Once unsubscribed, the previously registered handler will no longer be called. | | c | [accel\_tap\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_tap_service_subscribe) | | Subscribe to the accelerometer tap event service. Once subscribed, the handler gets called on every tap event emitted by the accelerometer. | | c | [accel\_tap\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_tap_service_unsubscribe) | | Unsubscribe from the accelerometer tap event service. Once unsubscribed, the previously registered handler will no longer be called. | | c | [accel\_raw\_data\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#accel_raw_data_service_subscribe) | | Subscribe to the accelerometer raw data event service. Once subscribed, the handler gets called every time there are new accelerometer samples available. | | c | [AccelData](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelData) | | A single accelerometer sample for all three axes including timestamp and vibration rumble status. | | c | [AccelRawData](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md#AccelRawData) | | A single accelerometer sample for all three axes. | | c | [AlarmService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md) | | | | c | [alarm\_service\_peek\_next](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md#alarm_service_peek_next) | | Peek at the next scheduled, enabled alarm. | | c | [AppFocusService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md) | | | | c | [AppFocusHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandler) | | Callback type for focus events. | | c | [app\_focus\_service\_subscribe\_handlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#app_focus_service_subscribe_handlers) | | Subscribe to the focus event service. Once subscribed, the handlers get called every time the app gains or loses focus. | | c | [app\_focus\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#app_focus_service_subscribe) | | Subscribe to the focus event service. Once subscribed, the handler gets called every time the app focus changes. | | c | [app\_focus\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#app_focus_service_unsubscribe) | | Unsubscribe from the focus event service. Once unsubscribed, the previously registered handlers will no longer be called. | | c | [AppFocusHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md#AppFocusHandlers) | | There are two different focus events which take place when transitioning to and from an app being in focus. Below is an example of when these events will occur: 1) The app is launched. Once the system animation to the app has completed and the app is completely in focus, the did\_focus handler is called with in\_focus set to true. 2) A notification comes in and the animation to show the notification starts. The will\_focus handler is called with in\_focus set to false. 3) The animation completes and the notification is in focus, with the app being completely covered. The did\_focus hander is called with in\_focus set to false. 4) The notification is dismissed and the animation to return to the app starts. The will\_focus handler is called with in\_focus set to true. 5) The animation completes and the app is in focus. The did\_focus handler is called with in\_focus set to true. | | c | [BacklightService](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md) | | | | c | [BacklightHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#BacklightHandler) | | Callback type for backlight on/off events. | | c | [backlight\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#backlight_service_subscribe) | | Subscribe to the backlight event service. Once subscribed, the handler is called every time the backlight transitions between off and on. | | c | [backlight\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md#backlight_service_unsubscribe) | | Unsubscribe from the backlight event service. Once unsubscribed, the previously registered handler will no longer be called. | | c | [BatteryStateService](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md) | | | | c | [BatteryStateHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryStateHandler) | | Callback type for battery state change events. | | c | [battery\_state\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#battery_state_service_subscribe) | | Subscribe to the battery state event service. Once subscribed, the handler gets called on every battery state change. | | c | [battery\_state\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#battery_state_service_unsubscribe) | | Unsubscribe from the battery state event service. Once unsubscribed, the previously registered handler will no longer be called. | | c | [battery\_state\_service\_peek](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#battery_state_service_peek) | | Peek at the last known battery state. | | c | [BatteryChargeState](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md#BatteryChargeState) | | Structure for retrieval of the battery charge state. | | c | [CompassService](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md) | | | | c | [CompassStatus](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatus) | | Enum describing the current state of the Compass Service. | | c | [CompassStatusUnavailable](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusUnavailable) | | The Compass Service is unavailable. | | c | [CompassStatusDataInvalid](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusDataInvalid) | | Compass is calibrating: data is invalid and should not be used Data will become valid once calibration is complete. | | c | [CompassStatusCalibrating](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusCalibrating) | | Compass is calibrating: the data is valid but the calibration is still being refined. | | c | [CompassStatusCalibrated](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassStatusCalibrated) | | Compass data is valid and the calibration has completed. | | c | [CompassHeading](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeading) | | Represents an angle relative to get to a reference direction, e.g. (magnetic) north. The angle value is scaled linearly, such that a value of TRIG\_MAX\_ANGLE corresponds to 360 degrees or 2 PI radians. Thus, if heading towards north, north is 0, west is TRIG\_MAX\_ANGLE/4, south is TRIG\_MAX\_ANGLE/2, and so on. | | c | [CompassHeadingHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingHandler) | | Callback type for compass heading events. | | c | [compass\_service\_set\_heading\_filter](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_set_heading_filter) | | Set the minimum angular change required to generate new compass heading events. The angular distance is measured relative to the last delivered heading event. Use 0 to be notified of all movements. Negative values and values \> TRIG\_MAX\_ANGLE / 2 are not valid. The default value of this property is TRIG\_MAX\_ANGLE / 360. | | c | [compass\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_subscribe) | | Subscribe to the compass heading event service. Once subscribed, the handler gets called every time the angular distance relative to the previous value exceeds the configured filter. | | c | [compass\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_unsubscribe) | | Unsubscribe from the compass heading event service. Once unsubscribed, the previously registered handler will no longer be called. | | c | [compass\_service\_peek](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#compass_service_peek) | | Peek at the last recorded reading. | | c | [CompassHeadingData](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md#CompassHeadingData) | | Structure containing a single heading towards magnetic and true north. | | c | [ConnectionService](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md) | | Determine what the Pebble watch is connected to | | c | [ConnectionHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#ConnectionHandler) | | | | c | [connection\_service\_peek\_pebble\_app\_connection](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#connection_service_peek_pebble_app_connection) | | Query the bluetooth connection service for the current Pebble app connection status. | | c | [connection\_service\_peek\_pebblekit\_connection](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#connection_service_peek_pebblekit_connection) | | Query the bluetooth connection service for the current PebbleKit connection status. | | c | [connection\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#connection_service_subscribe) | | Subscribe to the connection event service. Once subscribed, the appropriate handler gets called based on the type of connection event and user provided handlers. | | c | [connection\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#connection_service_unsubscribe) | | Unsubscribe from the bluetooth event service. Once unsubscribed, the previously registered handler will no longer be called. | | c | [BluetoothConnectionHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#BluetoothConnectionHandler) | | | | c | [bluetooth\_connection\_service\_peek](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#bluetooth_connection_service_peek) | | | | c | [bluetooth\_connection\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#bluetooth_connection_service_subscribe) | | | | c | [bluetooth\_connection\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#bluetooth_connection_service_unsubscribe) | | | | c | [ConnectionHandlers](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md#ConnectionHandlers) | | | | c | [HealthService](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md) | | | | c | [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) | | Health metric values used to retrieve health data. For example, using [health\_service\_sum()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum). | | c | [HealthMetricStepCount](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricStepCount) | | The number of steps counted. | | c | [HealthMetricActiveSeconds](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricActiveSeconds) | | The number of seconds spent active (i.e. not resting). | | c | [HealthMetricWalkedDistanceMeters](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricWalkedDistanceMeters) | | The distance walked, in meters. | | c | [HealthMetricSleepSeconds](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricSleepSeconds) | | The number of seconds spent sleeping. | | c | [HealthMetricSleepRestfulSeconds](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricSleepRestfulSeconds) | | The number of sleep seconds in the 'restful' or deep sleep state. | | c | [HealthMetricRestingKCalories](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricRestingKCalories) | | The number of kcal (Calories) burned while resting due to resting metabolism. | | c | [HealthMetricActiveKCalories](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricActiveKCalories) | | The number of kcal (Calories) burned while active. | | c | [HealthMetricHeartRateBPM](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateBPM) | | The heart rate, in beats per minute. This is a filtered value that is at most 15 minutes old. | | c | [HealthMetricHeartRateRawBPM](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricHeartRateRawBPM) | | The raw heart rate value of the most recent sample, in beats per minute. | | c | [HealthValue](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthValue) | | Type used to represent [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) values. | | c | [HealthMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert) | | Type used as a handle to a registered metric alert (returned by [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert)) | | c | [health\_service\_sum](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum) | | Return the sum of a [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric)'s values over a time range. The `time_start` and `time_end` parameters define the range of time you want the sum for. | | c | [health\_service\_sum\_today](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_today) | | Convenience wrapper for [health\_service\_sum()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum) that returns the sum for today. | | c | [health\_service\_peek\_current\_value](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_peek_current_value) | | Convenience function for peeking at the current value of a metric. This is useful for metrics like HealthMetricHeartRateBPM that represent instantaneous values. It is NOT applicable for metrics like HealthMetricStepCount that must be accumulated over time (it will return 0 if passed that type of metric). This call is equivalent to calling `health_service_aggregate_averaged(metric, time(NULL), time(NULL), HealthAggregationAvg, HealthServiceTimeScopeOnce)` | | c | [HealthServiceTimeScope](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScope) | | Used by [health\_service\_sum\_averaged()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged) to specify how the average is computed. | | c | [HealthServiceTimeScopeOnce](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScopeOnce) | | No average computed. The result is the same as calling [health\_service\_sum()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum). | | c | [HealthServiceTimeScopeWeekly](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScopeWeekly) | | Compute average using the same day from each week. For example, every Monday if the passed in time range falls on a Monday. | | c | [HealthServiceTimeScopeDailyWeekdayOrWeekend](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScopeDailyWeekdayOrWeekend) | | Compute average using either weekdays (Monday to Friday) or weekends (Saturday and Sunday), depending on which day the passed in time range falls. | | c | [HealthServiceTimeScopeDaily](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceTimeScopeDaily) | | Compute average across all days of the week. | | c | [health\_service\_sum\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged) | | Return the value of a metric's sum over a given time range between `time_start` and `time_end`. Using this call you can specify the time range that you are interested in getting the average for, as well as a `scope` specifier on how to compute an average of the sum. For example, if you want to get the average number of steps taken from 12 AM (midnight) to 9 AM across all days you would specify: | | c | [HealthAggregation](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregation) | | Used by [health\_service\_aggregate\_averaged()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged) to specify what type of aggregation to perform. This aggregation is applied to the metric before the average is computed. | | c | [HealthAggregationSum](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregationSum) | | Sum the metric. The result is the same as calling [health\_service\_sum\_averaged()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged). This operation is only applicable for metrics that accumulate, like HealthMetricStepCount, HealthMetricActiveSeconds, etc. | | c | [HealthAggregationAvg](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregationAvg) | | Use the average of the metric. This is only applicable for metrics that measure instantaneous values, like HealthMetricHeartRateBPM. | | c | [HealthAggregationMin](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregationMin) | | Use the minimum value of the metric. This is only applicable for metrics that measure instantaneous values, like HealthMetricHeartRateBPM. | | c | [HealthAggregationMax](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthAggregationMax) | | Use the maximum value of the metric. This is only applicable for metrics that measure instantaneous values, like HealthMetricHeartRateBPM. | | c | [health\_service\_aggregate\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged) | | Return the value of an aggregated metric over a given time range. This call is more flexible than health\_service\_sum\_averaged because it lets you specify which aggregation function to perform. | | c | [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) | | Expresses a set of [HealthActivity](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivity) values as a bitmask. | | c | [HealthActivityMaskAll](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMaskAll) | | A mask value representing all available activities. | | c | [HealthActivity](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivity) | | Health-related activities that can be accessed using. | | c | [HealthActivityNone](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityNone) | | No special activity. | | c | [HealthActivitySleep](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivitySleep) | | The 'sleeping' activity. | | c | [HealthActivityRestfulSleep](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityRestfulSleep) | | The 'restful sleeping' activity. | | c | [HealthActivityWalk](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityWalk) | | The 'walk' activity. | | c | [HealthActivityRun](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityRun) | | The 'run' activity. | | c | [HealthActivityOpenWorkout](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityOpenWorkout) | | The 'generic' activity. | | c | [health\_service\_peek\_current\_activities](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_peek_current_activities) | | Return a [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) containing a set of bits, one set for each activity that is currently active. | | c | [HealthActivityIteratorCB](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityIteratorCB) | | Callback used by [health\_service\_activities\_iterate()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_activities_iterate). | | c | [HealthIterationDirection](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthIterationDirection) | | Iteration direction, passed to [health\_service\_activities\_iterate()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_activities_iterate). When iterating backwards (`HealthIterationDirectionPast`), activities that have a greater value for `time_end` come first. When iterating forward (`HealthIterationDirectionFuture`), activities that have a smaller value for `time_start` come first. | | c | [HealthIterationDirectionPast](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthIterationDirectionPast) | | Iterate into the past. | | c | [HealthIterationDirectionFuture](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthIterationDirectionFuture) | | Iterate into the future. | | c | [health\_service\_activities\_iterate](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_activities_iterate) | | Iterates backwards or forward within a given time span to list all recorded activities. For example, this can be used to find the last recorded sleep phase or all deep sleep phases in a given time range. Any activity that overlaps with `time_start` and `time_end` will be included, even if the start time starts before `time_start` or end time ends after `time_end`. | | c | [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) | | Possible values returned by [health\_service\_metric\_accessible()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible). The values are used in combination as a bitmask. For example, to check if any data is available for a given request use: bool any\_data\_available = value & HealthServiceAccessibilityMaskAvailable;. | | c | [HealthServiceAccessibilityMaskAvailable](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMaskAvailable) | | Return values are available and represent the collected health information. | | c | [HealthServiceAccessibilityMaskNoPermission](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMaskNoPermission) | | The user hasn't granted permission. | | c | [HealthServiceAccessibilityMaskNotSupported](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMaskNotSupported) | | The queried combination of time span and [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) or [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) is currently unsupported. | | c | [HealthServiceAccessibilityMaskNotAvailable](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMaskNotAvailable) | | No samples were recorded for the given time span. | | c | [health\_service\_metric\_accessible](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible) | | Check if a certain combination of metric and time span is accessible using [health\_service\_sum](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum) by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_sum](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum). | | c | [health\_service\_metric\_averaged\_accessible](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_averaged_accessible) | | Check if a certain combination of metric, time span, and scope is accessible for calculating summed, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_sum\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_sum_averaged). | | c | [health\_service\_metric\_aggregate\_averaged\_accessible](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_aggregate_averaged_accessible) | | Check if a certain combination of metric, time span, aggregation operation, and scope is accessible for calculating aggregated, averaged data by returning a value of [HealthServiceAccessibilityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask). Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling [health\_service\_aggregate\_averaged](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_aggregate_averaged). | | c | [health\_service\_any\_activity\_accessible](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_any_activity_accessible) | | Check if a certain combination of metric, [HealthActivityMask](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthActivityMask) and time span is accessible. Developers should check if the return value is HealthServiceAccessibilityMaskAvailable before calling any other HealthService APIs that involve the given activities. | | c | [HealthEventType](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventType) | | Health event enum. Passed into the [HealthEventHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHandler). | | c | [HealthEventSignificantUpdate](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventSignificantUpdate) | | All data is considered as outdated and apps should re-read all health data. This happens after an app is subscribed via [health\_service\_events\_subscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_subscribe), on a change of the day, or in other cases that significantly change the underlying data. | | c | [HealthEventMovementUpdate](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventMovementUpdate) | | Recent values around HealthMetricStepCount, HealthMetricActiveSeconds, or HealthMetricWalkedDistanceMeters have changed. | | c | [HealthEventSleepUpdate](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventSleepUpdate) | | Recent values around HealthMetricSleepSeconds, HealthMetricSleepRestfulSeconds, HealthActivitySleep, and HealthActivityRestfulSleep changed. | | c | [HealthEventMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventMetricAlert) | | A metric has crossed the threshold set by [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert). | | c | [HealthEventHeartRateUpdate](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHeartRateUpdate) | | Value of HealthMetricHeartRateBPM or HealthMetricHeartRateRawBPM has changed. | | c | [HealthEventHRVUpdate](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHRVUpdate) | | A new HRV peak-to-peak interval reading is available. | | c | [HealthEventHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthEventHandler) | | Developer-supplied event handler, called when a health-related event occurs after subscribing via [health\_service\_events\_subscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_subscribe);. | | c | [health\_service\_events\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_subscribe) | | Subscribe to HealthService events. This allocates a cache on the application's heap of up to 2048 bytes that will be de-allocated if you call [health\_service\_events\_unsubscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_unsubscribe). If there's not enough heap available, this function will return `false` and will not subscribe to any events. | | c | [health\_service\_events\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_events_unsubscribe) | | Unsubscribe from HealthService events. | | c | [health\_service\_set\_heart\_rate\_sample\_period](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_set_heart_rate_sample_period) | | Set the desired sampling period for heart rate readings. Normally, the system will sample the heart rate using a sampling period that is automatically chosen to provide useful information without undue battery drain (it automatically samples more often during periods of intense activity, and less often when the user is idle). If desired though, an application can request a specific sampling period using this call. The system will use this as a suggestion, but does not guarantee that the requested period will be used. The actual sampling period may be greater or less due to system needs or heart rate sensor reading quality issues. | | c | [health\_service\_get\_heart\_rate\_sample\_period\_expiration\_sec](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_heart_rate_sample_period_expiration_sec) | | Return how long a heart rate sample period request (sent via `health_service_set_heart_rate_sample_period`) will remain active after the app exits. If there is no current request by this app, this call will return 0. | | c | [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert) | | Register for an alert when a metric crosses the given threshold. When the metric crosses this threshold (either goes above or below it), a HealthEventMetricAlert event will be generated. To cancel this registration, pass the returned [HealthMetricAlert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetricAlert) value to [health\_service\_cancel\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_cancel_metric_alert). The only metric currently supported by this call is HealthMetricHeartRateBPM, but future versions may support additional metrics. To see if a specific metric is supported by this call, use: | | c | [health\_service\_cancel\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_cancel_metric_alert) | | Cancel an metric alert previously created with [health\_service\_register\_metric\_alert](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_register_metric_alert). | | c | [AmbientLightLevel](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevel) | | Light level enum. | | c | [AmbientLightLevelUnknown](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevelUnknown) | | | | c | [AmbientLightLevelVeryDark](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevelVeryDark) | | | | c | [AmbientLightLevelDark](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevelDark) | | | | c | [AmbientLightLevelLight](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevelLight) | | | | c | [AmbientLightLevelVeryLight](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#AmbientLightLevelVeryLight) | | | | c | [health\_service\_get\_minute\_history](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_minute_history) | | Return historical minute data records. This fills in the `minute_data` array parameter with minute by minute statistics of the user's steps, average watch orientation, etc. The data is returned in time order, with the oldest minute data returned at `minute_data[0]`. | | c | [PBL\_IF\_HEALTH\_ELSE](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#PBL_IF_HEALTH_ELSE) | | Convenience macro to switch between two expressions depending on health support. On platforms with health support the first expression will be chosen, the second otherwise. | | c | [MeasurementSystem](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystem) | | Types of measurement system a [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) may be measured in. | | c | [MeasurementSystemUnknown](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystemUnknown) | | The measurement system is unknown, or does not apply to the chosen metric. | | c | [MeasurementSystemMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystemMetric) | | The metric measurement system. | | c | [MeasurementSystemImperial](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#MeasurementSystemImperial) | | The imperial measurement system. | | c | [health\_service\_get\_measurement\_system\_for\_display](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_measurement_system_for_display) | | Get the preferred measurement system for a given [HealthMetric](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric), if the user has chosen a preferred system and it is applicable to that metric. | | c | [HealthMinuteData](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMinuteData) | | Structure representing a single minute data record returned by [health\_service\_get\_minute\_history()](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_get_minute_history). The `orientation` field encodes the angle of the watch in the x-y plane (the "yaw") in the lower 4 bits (360 degrees linearly mapped to 1 of 16 different values) and the angle to the z axis (the "pitch") in the upper 4 bits. The `vmc` value is a measure of the total amount of movement seen by the watch. More vigorous movement yields higher VMC values. | | c | [TickTimerService](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md) | | Handling time components | | c | [TimeUnits](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TimeUnits) | | Time unit flags that can be used to create a bitmask for use in [tick\_timer\_service\_subscribe()](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#tick_timer_service_subscribe). This will also be passed to [TickHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler). | | c | [SECOND\_UNIT](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#SECOND_UNIT) | | Flag to represent the "seconds" time unit. | | c | [MINUTE\_UNIT](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#MINUTE_UNIT) | | Flag to represent the "minutes" time unit. | | c | [HOUR\_UNIT](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#HOUR_UNIT) | | Flag to represent the "hours" time unit. | | c | [DAY\_UNIT](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#DAY_UNIT) | | Flag to represent the "days" time unit. | | c | [MONTH\_UNIT](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#MONTH_UNIT) | | Flag to represent the "months" time unit. | | c | [YEAR\_UNIT](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#YEAR_UNIT) | | Flag to represent the "years" time unit. | | c | [TickHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler) | | Callback type for tick timer events. | | c | [tick\_timer\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#tick_timer_service_subscribe) | | Subscribe to the tick timer event service. Once subscribed, the handler gets called on every requested unit change. Calling this function multiple times will override the units and handler (i.e., only  the last tick\_units and handler passed will be used). | | c | [tick\_timer\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#tick_timer_service_unsubscribe) | | Unsubscribe from the tick timer event service. Once unsubscribed, the previously registered handler will no longer be called. | | c | [TouchService](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md) | | | | c | [TouchEventType](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEventType) | | Touch event type. | | c | [TouchEvent\_Touchdown](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_Touchdown) | | | | c | [TouchEvent\_Liftoff](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_Liftoff) | | | | c | [TouchEvent\_PositionUpdate](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_PositionUpdate) | | | | c | [TouchServiceHandler](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchServiceHandler) | | Callback for touch events. | | c | [touch\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#touch_service_subscribe) | | Subscribe to touch events. The touch sensor is enabled while subscribed. | | c | [touch\_service\_unsubscribe](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#touch_service_unsubscribe) | | Unsubscribe from touch events. The touch sensor is disabled if no other subscribers remain. | | c | [touch\_service\_is\_enabled](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#touch_service_is_enabled) | | | | c | [app\_touch\_navigation\_enable](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#app_touch_navigation_enable) | | Opt this app into (or out of) touch navigation: the system gesture bridge that maps swipes/taps on a MenuLayer/ScrollLayer to button presses. Third-party apps are opt-out by default and receive no touch navigation even when the user has enabled it system-wide; call this with _enable_ true to participate. Takes effect immediately when touch navigation is enabled system-wide, otherwise once the user enables it. | | c | [TouchEvent](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent) | | Touch event data, carried directly in PebbleTouchEvent. | | c | [Exit Reason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md) | | API for the application to notify the system of the reason it will exit. | | c | [AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) | | [AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) is used to notify the system of the reason of an application exiting, which may affect the part of the system UI that is presented after the application terminates. | | c | [APP\_EXIT\_NOT\_SPECIFIED](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#APP_EXIT_NOT_SPECIFIED) | | Exit reason not specified. | | c | [APP\_EXIT\_ACTION\_PERFORMED\_SUCCESSFULLY](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY) | | Application performed an action when it exited. | | c | [NUM\_EXIT\_REASONS](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#NUM_EXIT_REASONS) | | Number of [AppExitReason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason) options. | | c | [exit\_reason\_set](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#exit_reason_set) | | Set the app exit reason to a new reason. | | c | [Internationalization](https://developer.repebble.com/docs/c/Foundation/Internationalization.md) | | Internationalization & Localization APIs | | c | [i18n\_get\_system\_locale](https://developer.repebble.com/docs/c/Foundation/Internationalization.md#i18n_get_system_locale) | | Get the ISO locale name for the language currently set on the watch. | | c | [Launch Reason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md) | | API for checking what caused the application to launch. | | c | [AppLaunchReason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppLaunchReason) | | [AppLaunchReason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppLaunchReason) is used to inform the application about how it was launched. | | c | [APP\_LAUNCH\_SYSTEM](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_SYSTEM) | | App launched by the system. | | c | [APP\_LAUNCH\_USER](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_USER) | | App launched by user selection in launcher menu. | | c | [APP\_LAUNCH\_PHONE](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_PHONE) | | App launched by mobile or companion app. | | c | [APP\_LAUNCH\_WAKEUP](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_WAKEUP) | | App launched by wakeup event. | | c | [APP\_LAUNCH\_WORKER](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_WORKER) | | App launched by worker calling [worker\_launch\_app()](https://developer.repebble.com/docs/c/Worker.md#worker_launch_app) | | c | [APP\_LAUNCH\_QUICK\_LAUNCH](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_QUICK_LAUNCH) | | App launched by user using quick launch. | | c | [APP\_LAUNCH\_TIMELINE\_ACTION](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_TIMELINE_ACTION) | | App launched by user opening it from a pin. | | c | [APP\_LAUNCH\_SMARTSTRAP](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_SMARTSTRAP) | | App launched by a smartstrap. | | c | [launch\_reason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_reason) | | Provides the method used to launch the current application. | | c | [launch\_get\_args](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_get_args) | | Get the argument passed to the app when it was launched. | | c | [AppQuickLaunchAction](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#AppQuickLaunchAction) | | Details about how an app was quick launched. Returned by app\_launch\_get\_quick\_launch\_action. | | c | [APP\_QUICK\_LAUNCH\_ACTION\_NONE](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_NONE) | | App was not launched via Quick Launch. | | c | [APP\_QUICK\_LAUNCH\_ACTION\_HOLD](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_HOLD) | | User held a single button. | | c | [APP\_QUICK\_LAUNCH\_ACTION\_TAP](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_TAP) | | User tapped a button (single click) | | c | [APP\_QUICK\_LAUNCH\_ACTION\_COMBO](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_QUICK_LAUNCH_ACTION_COMBO) | | User held a button combination. | | c | [launch\_button](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_button) | | Get the button id used to launch the app. Only valid if the launch reason is APP\_LAUNCH\_USER or APP\_LAUNCH\_QUICK\_LAUNCH. | | c | [launch\_get\_quick\_launch\_action](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#launch_get_quick_launch_action) | | Get the action that was used to quick launch the app. | | c | [Logging](https://developer.repebble.com/docs/c/Foundation/Logging.md) | | Functions related to logging from apps. | | c | [app\_log](https://developer.repebble.com/docs/c/Foundation/Logging.md#app_log) | | | | c | [APP\_LOG](https://developer.repebble.com/docs/c/Foundation/Logging.md#APP_LOG) | | A helper macro that simplifies the use of the app\_log function. | | c | [AppLogLevel](https://developer.repebble.com/docs/c/Foundation/Logging.md#AppLogLevel) | | Suggested log level values. | | c | [APP\_LOG\_LEVEL\_ERROR](https://developer.repebble.com/docs/c/Foundation/Logging.md#APP_LOG_LEVEL_ERROR) | | Error level log message. | | c | [APP\_LOG\_LEVEL\_WARNING](https://developer.repebble.com/docs/c/Foundation/Logging.md#APP_LOG_LEVEL_WARNING) | | Warning level log message. | | c | [APP\_LOG\_LEVEL\_INFO](https://developer.repebble.com/docs/c/Foundation/Logging.md#APP_LOG_LEVEL_INFO) | | Info level log message. | | c | [APP\_LOG\_LEVEL\_DEBUG](https://developer.repebble.com/docs/c/Foundation/Logging.md#APP_LOG_LEVEL_DEBUG) | | Debug level log message. | | c | [APP\_LOG\_LEVEL\_DEBUG\_VERBOSE](https://developer.repebble.com/docs/c/Foundation/Logging.md#APP_LOG_LEVEL_DEBUG_VERBOSE) | | Verbose Debug level log message. | | c | [Math](https://developer.repebble.com/docs/c/Foundation/Math.md) | | Math routines. | | c | [TRIG\_MAX\_RATIO](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_RATIO) | | The largest value that can result from a call to [sin\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#sin_lookup) or [cos\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#cos_lookup). For a code example, see the detailed description at the top of this chapter: [Math](https://developer.repebble.com/docs/c/Foundation/Math.md). | | c | [TRIG\_MAX\_ANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIG_MAX_ANGLE) | | Angle value that corresponds to 360 degrees or 2 PI radians. | | c | [TRIGANGLE\_TO\_DEG](https://developer.repebble.com/docs/c/Foundation/Math.md#TRIGANGLE_TO_DEG) | | Converts from a fixed point value representation to the equivalent value in degrees. | | c | [DEG\_TO\_TRIGANGLE](https://developer.repebble.com/docs/c/Foundation/Math.md#DEG_TO_TRIGANGLE) | | Converts from an angle in degrees to the equivalent fixed point value representation. | | c | [sin\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#sin_lookup) | | Look-up the sine of the given angle from a pre-computed table. | | c | [cos\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#cos_lookup) | | Look-up the cosine of the given angle from a pre-computed table. This is equivalent to calling `sin_lookup(angle + TRIG_MAX_ANGLE / 4)`. | | c | [atan2\_lookup](https://developer.repebble.com/docs/c/Foundation/Math.md#atan2_lookup) | | Look-up the arctangent of a given x, y pair The angle value is scaled linearly, such that a value of 0x10000 corresponds to 360 degrees or 2 PI radians. | | c | [Memory Management](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md) | | Utility functions for managing an application's memory. | | c | [heap\_bytes\_free](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md#heap_bytes_free) | | Calculates the number of bytes of heap memory _not_ currently being used by the application. | | c | [heap\_bytes\_used](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md#heap_bytes_used) | | Calculates the number of bytes of heap memory currently being used by the application. | | c | [memory\_cache\_flush](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md#memory_cache_flush) | | Flushes the data cache and invalidates the instruction cache for the given region of memory, if necessary. This is only required when your app is loading or modifying code in memory and intends to execute it. On some platforms, code executed may be cached internally to improve performance. After writing to memory, but before executing, this function must be called in order to avoid undefined behavior. On platforms without caching, this performs no operation. | | c | [Platform](https://developer.repebble.com/docs/c/Foundation/Platform.md) | | | | c | [PlatformType](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformType) | | | | c | [PlatformTypeAplite](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformTypeAplite) | | | | c | [PlatformTypeBasalt](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformTypeBasalt) | | | | c | [PlatformTypeChalk](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformTypeChalk) | | | | c | [PlatformTypeDiorite](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformTypeDiorite) | | | | c | [PlatformTypeEmery](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformTypeEmery) | | | | c | [PlatformTypeFlint](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformTypeFlint) | | | | c | [PlatformTypeGabbro](https://developer.repebble.com/docs/c/Foundation/Platform.md#PlatformTypeGabbro) | | | | c | [PBL\_PLATFORM\_TYPE\_CURRENT](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_TYPE_CURRENT) | | | | c | [PBL\_PLATFORM\_SWITCH\_DEFAULT](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_SWITCH_DEFAULT) | | | | c | [PBL\_PLATFORM\_SWITCH](https://developer.repebble.com/docs/c/Foundation/Platform.md#PBL_PLATFORM_SWITCH) | | | | c | [Resources](https://developer.repebble.com/docs/c/Foundation/Resources.md) | | Managing application resources | | c | [ResHandle](https://developer.repebble.com/docs/c/Foundation/Resources.md#ResHandle) | | Opaque reference to a resource. | | c | [RESOURCE\_ID\_FONT\_FALLBACK](https://developer.repebble.com/docs/c/Foundation/Resources.md#RESOURCE_ID_FONT_FALLBACK) | | | | c | [resource\_get\_handle](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_get_handle) | | Gets the resource handle for a file identifier. | | c | [resource\_size](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_size) | | Gets the size of the resource given a resource handle. | | c | [resource\_load](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_load) | | Copies the bytes for the resource with a given handle from flash storage into a given buffer. | | c | [resource\_load\_byte\_range](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_load_byte_range) | | Copies a range of bytes from a resource with a given handle into a given buffer. | | c | [File Formats](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats.md) | | | | c | [PBI File Format](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PBI_File_Format.md) | | | | c | [PNG8 File Format](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PNG8_File_Format.md) | | | | c | [Storage](https://developer.repebble.com/docs/c/Foundation/Storage.md) | | A mechanism to store persistent application data and state | | c | [PERSIST\_DATA\_MAX\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_DATA_MAX_LENGTH) | | The maximum size of a persist value in bytes. | | c | [PERSIST\_STRING\_MAX\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_STRING_MAX_LENGTH) | | The maximum size of a persist string in bytes including the NULL terminator. | | c | [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode) | | Status codes. See [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t). | | c | [S\_SUCCESS](https://developer.repebble.com/docs/c/Foundation/Storage.md#S_SUCCESS) | | Operation completed successfully. | | c | [E\_ERROR](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_ERROR) | | An error occurred (no description). | | c | [E\_UNKNOWN](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_UNKNOWN) | | No idea what went wrong. | | c | [E\_INTERNAL](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_INTERNAL) | | There was a generic internal logic error. | | c | [E\_INVALID\_ARGUMENT](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_INVALID_ARGUMENT) | | The function was not called correctly. | | c | [E\_OUT\_OF\_MEMORY](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_OUT_OF_MEMORY) | | Insufficient allocatable memory available. | | c | [E\_OUT\_OF\_STORAGE](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_OUT_OF_STORAGE) | | Insufficient long-term storage available. | | c | [E\_OUT\_OF\_RESOURCES](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_OUT_OF_RESOURCES) | | Insufficient resources available. | | c | [E\_RANGE](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_RANGE) | | Argument out of range (may be dynamic). | | c | [E\_DOES\_NOT\_EXIST](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_DOES_NOT_EXIST) | | Target of operation does not exist. | | c | [E\_INVALID\_OPERATION](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_INVALID_OPERATION) | | Operation not allowed (may depend on state). | | c | [E\_BUSY](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_BUSY) | | Another operation prevented this one. | | c | [E\_AGAIN](https://developer.repebble.com/docs/c/Foundation/Storage.md#E_AGAIN) | | Operation not completed; try again. | | c | [S\_TRUE](https://developer.repebble.com/docs/c/Foundation/Storage.md#S_TRUE) | | Equivalent of boolean true. | | c | [S\_FALSE](https://developer.repebble.com/docs/c/Foundation/Storage.md#S_FALSE) | | Equivalent of boolean false. | | c | [S\_NO\_MORE\_ITEMS](https://developer.repebble.com/docs/c/Foundation/Storage.md#S_NO_MORE_ITEMS) | | For list-style requests. At end of list. | | c | [S\_NO\_ACTION\_REQUIRED](https://developer.repebble.com/docs/c/Foundation/Storage.md#S_NO_ACTION_REQUIRED) | | No action was taken as none was required. | | c | [status\_t](https://developer.repebble.com/docs/c/Foundation/Storage.md#status_t) | | Return value for system operations. See [StatusCode](https://developer.repebble.com/docs/c/Foundation/Storage.md#StatusCode) for possible values. | | c | [persist\_exists](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_exists) | | Checks whether a value has been set for a given key in persistent storage. | | c | [persist\_get\_size](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_get_size) | | Gets the size of a value for a given key in persistent storage. | | c | [persist\_read\_bool](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_read_bool) | | Reads a bool value for a given key from persistent storage. If the value has not yet been set, this will return false. | | c | [persist\_read\_int](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_read_int) | | Reads an int value for a given key from persistent storage. | | c | [persist\_read\_data](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_read_data) | | Reads a blob of data for a given key from persistent storage into a given buffer. If the value has not yet been set, the given buffer is left unchanged. | | c | [persist\_read\_string](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_read_string) | | Reads a string for a given key from persistent storage into a given buffer. The string will be null terminated. If the value has not yet been set, the given buffer is left unchanged. | | c | [persist\_write\_bool](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_bool) | | Writes a bool value flag for a given key into persistent storage. | | c | [persist\_write\_int](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_int) | | Writes an int value for a given key into persistent storage. | | c | [persist\_write\_data](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_data) | | Writes a blob of data of a specified size in bytes for a given key into persistent storage. The maximum size is [PERSIST\_DATA\_MAX\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_DATA_MAX_LENGTH). | | c | [persist\_write\_string](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_write_string) | | Writes a string a given key into persistent storage. The maximum size is [PERSIST\_STRING\_MAX\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Storage.md#PERSIST_STRING_MAX_LENGTH) including the null terminator. | | c | [persist\_delete](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_delete) | | Deletes the value of a key from persistent storage. | | c | [persist\_get\_max\_size](https://developer.repebble.com/docs/c/Foundation/Storage.md#persist_get_max_size) | | Gets the maximum total size in bytes of all persisted values for the current app on this firmware. Apps targeting older SDKs that don't have this function should assume a 4 KB limit. | | c | [Timer](https://developer.repebble.com/docs/c/Foundation/Timer.md) | | Can be used to execute some code at some point in the future. | | c | [psleep](https://developer.repebble.com/docs/c/Foundation/Timer.md#psleep) | | Waits for a certain amount of milliseconds. | | c | [AppTimer](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimer) | | | | c | [AppTimerCallback](https://developer.repebble.com/docs/c/Foundation/Timer.md#AppTimerCallback) | | The type of function which can be called when a timer fires. The argument will be the `callback_data` passed to [app\_timer\_register()](https://developer.repebble.com/docs/c/Foundation/Timer.md#app_timer_register). | | c | [app\_timer\_register](https://developer.repebble.com/docs/c/Foundation/Timer.md#app_timer_register) | | Registers a timer that ends up in callback being called some specified time in the future. | | c | [app\_timer\_reschedule](https://developer.repebble.com/docs/c/Foundation/Timer.md#app_timer_reschedule) | | Reschedules an already running timer for some point in the future. Elapsed timers cannot be rescheduled. | | c | [app\_timer\_cancel](https://developer.repebble.com/docs/c/Foundation/Timer.md#app_timer_cancel) | | Cancels an already registered timer. Once cancelled the handle may no longer be used for any purpose. Elapsed timers do not need to be cancelled. | | c | [Wakeup](https://developer.repebble.com/docs/c/Foundation/Wakeup.md) | | Allows applications to schedule to be launched even if they are not running. | | c | [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) | | [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) is an identifier for a wakeup event. | | c | [WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) | | The type of function which can be called when a wakeup event occurs. The arguments will be the id of the wakeup event that occurred, as well as the scheduled cookie provided to `wakeup_schedule`. | | c | [wakeup\_service\_subscribe](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_service_subscribe) | | Registers a [WakeupHandler](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupHandler) to be called when wakeup events occur. | | c | [wakeup\_schedule](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_schedule) | | Registers a wakeup event that triggers a callback at the specified time. Applications may only schedule up to 8 wakeup events. Wakeup events are given a 1 minute duration window, in that no application may schedule a wakeup event with 1 minute of a currently scheduled wakeup event. | | c | [wakeup\_cancel](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_cancel) | | Cancels a wakeup event. | | c | [wakeup\_cancel\_all](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_cancel_all) | | Cancels all wakeup event for the app. | | c | [wakeup\_get\_launch\_event](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_get_launch_event) | | Retrieves the wakeup event info for an app that was launched by a wakeup\_event (ie. `launch_reason()` === APP\_LAUNCH\_WAKEUP) so that an app may display information regarding the wakeup event. | | c | [wakeup\_query](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#wakeup_query) | | Checks if the current [WakeupId](https://developer.repebble.com/docs/c/Foundation/Wakeup.md#WakeupId) is still scheduled and therefore valid. | | c | [Wall Time](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md) | | Functions, data structures and other things related to wall clock time. | | c | [WeekDay](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WeekDay) | | Weekday values. | | c | [TODAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#TODAY) | | Today. | | c | [SUNDAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#SUNDAY) | | Sunday. | | c | [MONDAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#MONDAY) | | Monday. | | c | [TUESDAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#TUESDAY) | | Tuesday. | | c | [WEDNESDAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#WEDNESDAY) | | Wednesday. | | c | [THURSDAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#THURSDAY) | | Thursday. | | c | [FRIDAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#FRIDAY) | | Friday. | | c | [SATURDAY](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#SATURDAY) | | Saturday. | | c | [clock\_copy\_time\_string](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_copy_time_string) | | Copies a time string into the buffer, formatted according to the user's time display preferences (such as 12h/24h time). Example results: "7:30" or "15:00". | | c | [clock\_is\_24h\_style](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_is_24h_style) | | Gets the user's 12/24h clock style preference. | | c | [clock\_to\_timestamp](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_to_timestamp) | | Converts a (day, hour, minute) specification to a UTC timestamp occurring in the future Always returns a timestamp for the next occurring instance, example: specifying TODAY@14:30 when it is 14:40 will return a timestamp for 7 days from now at 14:30. | | c | [clock\_is\_timezone\_set](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_is_timezone_set) | | Checks if timezone is currently set, otherwise gmtime == localtime. | | c | [TIMEZONE\_NAME\_LENGTH](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#TIMEZONE_NAME_LENGTH) | | The maximum length for a timezone full name (e.g. America/Chicago) | | c | [clock\_get\_timezone](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md#clock_get_timezone) | | If timezone is set, copies the current timezone long name (e.g. America/Chicago) to user-provided buffer. | | c | [WatchInfo](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md) | | Provides information about the watch itself. | | c | [WatchInfoModel](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoModel) | | The different watch models. | | c | [WATCH\_INFO\_MODEL\_UNKNOWN](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_UNKNOWN) | | Unknown model. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_ORIGINAL](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_ORIGINAL) | | Original Pebble. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_STEEL](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_STEEL) | | Pebble Steel. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_TIME](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_TIME) | | Pebble Time. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_STEEL](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_TIME_STEEL) | | Pebble Time Steel. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_ROUND\_14](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_TIME_ROUND_14) | | Pebble Time Round, 14mm lug size. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_ROUND\_20](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_TIME_ROUND_20) | | Pebble Time Round, 20mm lug size. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_2\_HR](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_2_HR) | | Pebble 2 HR. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_2\_SE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_2_SE) | | Pebble 2 SE. | | c | [WATCH\_INFO\_MODEL\_PEBBLE\_TIME\_2](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_PEBBLE_TIME_2) | | Pebble Time 2. | | c | [WATCH\_INFO\_MODEL\_COREDEVICES\_P2D](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_COREDEVICES_P2D) | | CoreDevices P2D (Pebble 2 Duo) | | c | [WATCH\_INFO\_MODEL\_COREDEVICES\_PT2](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_COREDEVICES_PT2) | | CoreDevices PT2 (Pebble Time 2) | | c | [WATCH\_INFO\_MODEL\_COREDEVICES\_PR2](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_COREDEVICES_PR2) | | CoreDevices PR2 (Pebble Round 2) | | c | [WATCH\_INFO\_MODEL\_\_MAX](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL__MAX) | | | | c | [WATCH\_INFO\_MODEL\_COREDEVICES\_C2D](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_COREDEVICES_C2D) | | CoreDevices C2D (Core 2 Duo) | | c | [WATCH\_INFO\_MODEL\_COREDEVICES\_CT2](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_MODEL_COREDEVICES_CT2) | | CoreDevices CT2 (Core Time 2) | | c | [WatchInfoColor](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoColor) | | The different watch colors. | | c | [WATCH\_INFO\_COLOR\_UNKNOWN](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_UNKNOWN) | | Unknown color. | | c | [WATCH\_INFO\_COLOR\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_BLACK) | | Black. | | c | [WATCH\_INFO\_COLOR\_WHITE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_WHITE) | | White. | | c | [WATCH\_INFO\_COLOR\_RED](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_RED) | | Red. | | c | [WATCH\_INFO\_COLOR\_ORANGE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_ORANGE) | | Orange. | | c | [WATCH\_INFO\_COLOR\_GRAY](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_GRAY) | | Gray. | | c | [WATCH\_INFO\_COLOR\_STAINLESS\_STEEL](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_STAINLESS_STEEL) | | Stainless Steel. | | c | [WATCH\_INFO\_COLOR\_MATTE\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_MATTE_BLACK) | | Matte Black. | | c | [WATCH\_INFO\_COLOR\_BLUE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_BLUE) | | Blue. | | c | [WATCH\_INFO\_COLOR\_GREEN](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_GREEN) | | Green. | | c | [WATCH\_INFO\_COLOR\_PINK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PINK) | | Pink. | | c | [WATCH\_INFO\_COLOR\_TIME\_WHITE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_WHITE) | | Time White. | | c | [WATCH\_INFO\_COLOR\_TIME\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_BLACK) | | Time Black. | | c | [WATCH\_INFO\_COLOR\_TIME\_RED](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_RED) | | Time Red. | | c | [WATCH\_INFO\_COLOR\_TIME\_STEEL\_SILVER](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_STEEL_SILVER) | | Time Steel Silver. | | c | [WATCH\_INFO\_COLOR\_TIME\_STEEL\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_STEEL_BLACK) | | Time Steel Black. | | c | [WATCH\_INFO\_COLOR\_TIME\_STEEL\_GOLD](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_STEEL_GOLD) | | Time Steel Gold. | | c | [WATCH\_INFO\_COLOR\_TIME\_ROUND\_SILVER\_14](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_ROUND_SILVER_14) | | Time Round 14mm lug size, Silver. | | c | [WATCH\_INFO\_COLOR\_TIME\_ROUND\_BLACK\_14](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_ROUND_BLACK_14) | | Time Round 14mm lug size, Black. | | c | [WATCH\_INFO\_COLOR\_TIME\_ROUND\_SILVER\_20](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_ROUND_SILVER_20) | | Time Round 20mm lug size, Silver. | | c | [WATCH\_INFO\_COLOR\_TIME\_ROUND\_BLACK\_20](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_ROUND_BLACK_20) | | Time Round 20mm lug size, Black. | | c | [WATCH\_INFO\_COLOR\_TIME\_ROUND\_ROSE\_GOLD\_14](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_TIME_ROUND_ROSE_GOLD_14) | | Time Round 14mm lug size, Rose Gold. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_2_HR_BLACK) | | Pebble 2 HR, Black / Charcoal. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_LIME](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_2_HR_LIME) | | Pebble 2 HR, Charcoal / Sorbet Green. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_FLAME](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_2_HR_FLAME) | | Pebble 2 HR, Charcoal / Red. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_WHITE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_2_HR_WHITE) | | Pebble 2 HR, White / Gray. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_2\_HR\_AQUA](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_2_HR_AQUA) | | Pebble 2 HR, White / Turquoise. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_2\_SE\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_2_SE_BLACK) | | Pebble 2 SE, Black / Charcoal. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_2\_SE\_WHITE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_2_SE_WHITE) | | Pebble 2 SE, White / Gray. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_TIME\_2\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_TIME_2_BLACK) | | Pebble Time 2, Black. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_TIME\_2\_SILVER](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_TIME_2_SILVER) | | Pebble Time 2, Silver. | | c | [WATCH\_INFO\_COLOR\_PEBBLE\_TIME\_2\_GOLD](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_PEBBLE_TIME_2_GOLD) | | Pebble Time 2, Gold. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_P2D\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_P2D_BLACK) | | CoreDevices P2D, Black. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_P2D\_WHITE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_P2D_WHITE) | | CoreDevices P2D, White. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_BLACK\_GREY](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PT2_BLACK_GREY) | | CoreDevices PT2, Black/Grey. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_BLACK\_RED](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PT2_BLACK_RED) | | CoreDevices PT2, Black/Red. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_SILVER\_BLUE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PT2_SILVER_BLUE) | | CoreDevices PT2, Silver/Blue. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PT2\_SILVER\_GREY](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PT2_SILVER_GREY) | | CoreDevices PT2, Silver/Grey. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_BLACK\_20](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PR2_BLACK_20) | | CoreDevices PR2, Black (20mm) | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_SILVER\_20](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PR2_SILVER_20) | | CoreDevices PR2, Silver (20mm) | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_GOLD\_14](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PR2_GOLD_14) | | CoreDevices PR2, Gold (14mm) | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_PR2\_SILVER\_14](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_PR2_SILVER_14) | | CoreDevices PR2, Silver (14mm) | | c | [WATCH\_INFO\_COLOR\_\_MAX](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR__MAX) | | | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_CT2\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_CT2_BLACK) | | CoreDevices CT2, Black. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_C2D\_WHITE](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_C2D_WHITE) | | CoreDevices C2D, White. | | c | [WATCH\_INFO\_COLOR\_COREDEVICES\_C2D\_BLACK](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WATCH_INFO_COLOR_COREDEVICES_C2D_BLACK) | | CoreDevices C2D, Black. | | c | [watch\_info\_get\_model](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#watch_info_get_model) | | Provides the model of the watch. | | c | [watch\_info\_get\_firmware\_version](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#watch_info_get_firmware_version) | | Provides the version of the firmware running on the watch. | | c | [watch\_info\_get\_color](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#watch_info_get_color) | | | | c | [WatchInfoVersion](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md#WatchInfoVersion) | | Data structure containing the version of the firmware running on the watch. The version of the firmware has the form X.[X.[X]]. If a version number is not present it will be 0. For example: the version numbers of 2.4.1 are 2, 4, and 1. The version numbers of 2.4 are 2, 4, and 0. | | c | [Graphics](https://developer.repebble.com/docs/c/Graphics.md) | | Low-level drawing routines. | | c | [Draw Commands](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md) | | Pebble Draw Commands are a way to encode arbitrary path draw and fill calls in binary format, so that vector-like graphics can be represented on the watch. | | c | [GDrawCommand](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommand) | | Draw commands are the basic building block of the draw command system, encoding the type of command to draw, the stroke width and color, fill color, and points that define the path (or center of a circle. | | c | [GDrawCommandFrame](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandFrame) | | Draw command frames contain a list of commands to draw for that frame and a duration, indicating the length of time for which the frame should be drawn in an animation sequence. Frames form the building blocks of a [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence), which consists of multiple frames. | | c | [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) | | Draw command images contain a list of commands that can be drawn. An image can be loaded from PDC file data. | | c | [GDrawCommandList](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandList) | | Draw command lists contain a list of commands that can be iterated over and drawn all at once. | | c | [GDrawCommandListIteratorCb](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandListIteratorCb) | | Callback for iterating over draw command list. | | c | [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) | | Draw command sequences allow the animation of frames over time. Each sequence has a list of frames that can be accessed by the elapsed duration of the animation (not maintained internally) or by index. Sequences can be loaded from PDC file data. | | c | [GDrawCommandType](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandType) | | | | c | [GDrawCommandTypeInvalid](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandTypeInvalid) | | Invalid draw command type. | | c | [GDrawCommandTypePath](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandTypePath) | | Arbitrary path draw command type. | | c | [GDrawCommandTypeCircle](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandTypeCircle) | | Circle draw command type. | | c | [GDrawCommandTypePrecisePath](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandTypePrecisePath) | | Arbitrary path drawn with sub-pixel precision (1/8th precision) | | c | [gdraw\_command\_draw](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_draw) | | Draw a command. | | c | [gdraw\_command\_get\_type](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_type) | | Get the command type. | | c | [gdraw\_command\_set\_fill\_color](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_set_fill_color) | | Set the fill color of a command. | | c | [gdraw\_command\_get\_fill\_color](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_fill_color) | | Get the fill color of a command. | | c | [gdraw\_command\_set\_stroke\_color](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_set_stroke_color) | | Set the stroke color of a command. | | c | [gdraw\_command\_get\_stroke\_color](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_stroke_color) | | Get the stroke color of a command. | | c | [gdraw\_command\_set\_stroke\_width](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_set_stroke_width) | | Set the stroke width of a command. | | c | [gdraw\_command\_get\_stroke\_width](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_stroke_width) | | Get the stroke width of a command. | | c | [gdraw\_command\_get\_num\_points](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_num_points) | | Get the number of points in a command. | | c | [gdraw\_command\_set\_point](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_set_point) | | Set the value of the point in a command at the specified index. | | c | [gdraw\_command\_get\_point](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_point) | | Get the value of a point in a command from the specified index. | | c | [gdraw\_command\_set\_radius](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_set_radius) | | Set the radius of a circle command. | | c | [gdraw\_command\_get\_radius](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_radius) | | Get the radius of a circle command. | | c | [gdraw\_command\_set\_path\_open](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_set_path_open) | | Set the path of a stroke command to be open. | | c | [gdraw\_command\_get\_path\_open](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_path_open) | | Return whether a stroke command path is open. | | c | [gdraw\_command\_set\_hidden](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_set_hidden) | | Set a command as hidden. This command will not be drawn when [gdraw\_command\_draw](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_draw) is called with this command. | | c | [gdraw\_command\_get\_hidden](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_get_hidden) | | Return whether a command is hidden. | | c | [gdraw\_command\_frame\_draw](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_frame_draw) | | Draw a frame. | | c | [gdraw\_command\_frame\_set\_duration](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_frame_set_duration) | | Set the duration of the frame. | | c | [gdraw\_command\_frame\_get\_duration](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_frame_get_duration) | | Get the duration of the frame. | | c | [gdraw\_command\_image\_create\_with\_resource](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_create_with_resource) | | Creates a [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) from the specified resource (PDC file) | | c | [gdraw\_command\_image\_clone](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_clone) | | Creates a [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) as a copy from a given image. | | c | [gdraw\_command\_image\_destroy](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_destroy) | | Deletes the [GDrawCommandImage](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandImage) structure and frees associated data. | | c | [gdraw\_command\_image\_draw](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_draw) | | Draw an image. | | c | [gdraw\_command\_image\_get\_bounds\_size](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_get_bounds_size) | | Get size of the bounding box surrounding all draw commands in the image. This bounding box can be used to set the graphics context or layer bounds when drawing the image. | | c | [gdraw\_command\_image\_set\_bounds\_size](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_set_bounds_size) | | Set size of the bounding box surrounding all draw commands in the image. This bounding box can be used to set the graphics context or layer bounds when drawing the image. | | c | [gdraw\_command\_image\_get\_command\_list](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_image_get_command_list) | | Get the command list of the image. | | c | [gdraw\_command\_list\_iterate](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_list_iterate) | | Iterate over all commands in a command list. | | c | [gdraw\_command\_list\_draw](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_list_draw) | | Draw all commands in a command list. | | c | [gdraw\_command\_list\_get\_command](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_list_get_command) | | Get the command at the specified index. | | c | [gdraw\_command\_list\_get\_num\_commands](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_list_get_num_commands) | | Get the number of commands in the list. | | c | [gdraw\_command\_sequence\_create\_with\_resource](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_create_with_resource) | | Creates a [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) from the specified resource (PDC file) | | c | [gdraw\_command\_sequence\_clone](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_clone) | | Creates a [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) as a copy from a given sequence. | | c | [gdraw\_command\_sequence\_destroy](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_destroy) | | Deletes the [GDrawCommandSequence](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#GDrawCommandSequence) structure and frees associated data. | | c | [gdraw\_command\_sequence\_get\_frame\_by\_elapsed](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_get_frame_by_elapsed) | | Get the frame that should be shown after the specified amount of elapsed time The last frame will be returned if the elapsed time exceeds the total time. | | c | [gdraw\_command\_sequence\_get\_frame\_by\_index](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_get_frame_by_index) | | Get the frame at the specified index. | | c | [gdraw\_command\_sequence\_get\_bounds\_size](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_get_bounds_size) | | Get the size of the bounding box surrounding all draw commands in the sequence. This bounding box can be used to set the graphics context or layer bounds when drawing the frames in the sequence. | | c | [gdraw\_command\_sequence\_set\_bounds\_size](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_set_bounds_size) | | Set size of the bounding box surrounding all draw commands in the sequence. This bounding box can be used to set the graphics context or layer bounds when drawing the frames in the sequence. | | c | [gdraw\_command\_sequence\_get\_play\_count](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_get_play_count) | | Get the play count of the sequence. | | c | [gdraw\_command\_sequence\_set\_play\_count](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_set_play_count) | | Set the play count of the sequence. | | c | [gdraw\_command\_sequence\_get\_total\_duration](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_get_total_duration) | | Get the total duration of the sequence. | | c | [gdraw\_command\_sequence\_get\_num\_frames](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_sequence_get_num_frames) | | Get the number of frames in the sequence. | | c | [gdraw\_command\_frame\_get\_command\_list](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md#gdraw_command_frame_get_command_list) | | Get the command list of the frame. | | c | [Drawing Paths](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md) | | Functions to draw polygons into a graphics context | | c | [gpath\_create](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_create) | | Creates a new [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) on the heap based on a series of points described by a [GPathInfo](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPathInfo). | | c | [gpath\_destroy](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_destroy) | | Free a dynamically allocated gpath created with [gpath\_create()](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_create) | | c | [gpath\_draw\_filled](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_filled) | | Draws the fill of a path into a graphics context, using the current fill color, relative to the drawing area as set up by the layering system. | | c | [gpath\_draw\_outline](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline) | | Draws the outline of a path into a graphics context, using the current stroke color and width, relative to the drawing area as set up by the layering system. The first and last points in the path do have a line between them. | | c | [gpath\_rotate\_to](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_rotate_to) | | Sets the absolute rotation of the path. The current rotation will be replaced by the specified angle. | | c | [gpath\_move\_to](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_move_to) | | Sets the absolute offset of the path. The current translation will be replaced by the specified offset. | | c | [gpath\_draw\_outline\_open](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#gpath_draw_outline_open) | | Draws an open outline of a path into a graphics context, using the current stroke color and width, relative to the drawing area as set up by the layering system. The first and last points in the path do not have a line between them. | | c | [GPath](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath) | | Data structure describing a path, plus its rotation and translation. | | c | [GPathInfo](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPathInfo) | | Data structure describing a naked path. | | c | [Drawing Primitives](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md) | | Functions to draw into a graphics context | | c | [GCornerMask](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerMask) | | Bit mask values to specify the corners of a rectangle. The values can be combines using binary OR (`|`), For example: the mask to indicate top left and bottom right corners can: be created as follows: `(GCornerTopLeft | GCornerBottomRight)` | | c | [GCornerNone](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerNone) | | No corners. | | c | [GCornerTopLeft](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerTopLeft) | | Top-Left corner. | | c | [GCornerTopRight](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerTopRight) | | Top-Right corner. | | c | [GCornerBottomLeft](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerBottomLeft) | | Bottom-Left corner. | | c | [GCornerBottomRight](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornerBottomRight) | | Bottom-Right corner. | | c | [GCornersAll](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornersAll) | | All corners. | | c | [GCornersTop](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornersTop) | | Top corners. | | c | [GCornersBottom](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornersBottom) | | Bottom corners. | | c | [GCornersLeft](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornersLeft) | | Left corners. | | c | [GCornersRight](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GCornersRight) | | Right corners. | | c | [graphics\_draw\_pixel](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_pixel) | | Draws a pixel at given point in the current stroke color. | | c | [graphics\_draw\_line](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_line) | | Draws line in the current stroke color, current stroke width and AA flag. | | c | [graphics\_draw\_rect](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_rect) | | Draws a 1-pixel wide rectangle outline in the current stroke color. | | c | [graphics\_fill\_rect](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_rect) | | Fills a rectangle with the current fill color, optionally rounding all or a selection of its corners. | | c | [graphics\_draw\_circle](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_circle) | | Draws the outline of a circle in the current stroke color. | | c | [graphics\_fill\_circle](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_circle) | | Fills a circle in the current fill color. | | c | [graphics\_draw\_round\_rect](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_round_rect) | | Draws the outline of a rounded rectangle in the current stroke color. | | c | [graphics\_draw\_bitmap\_in\_rect](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_bitmap_in_rect) | | Draws a bitmap into the graphics context, inside the specified rectangle. | | c | [graphics\_capture\_frame\_buffer](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_capture_frame_buffer) | | A shortcut to capture the framebuffer in the native format of the watch. | | c | [graphics\_capture\_frame\_buffer\_format](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_capture_frame_buffer_format) | | Captures the frame buffer for direct access, using the given format. Graphics functions will not affect the frame buffer while it is captured. The frame buffer is released when `graphics_release_frame_buffer` is called. The frame buffer must be released before the end of a layer's `.update_proc` for the layer to be drawn properly. | | c | [graphics\_release\_frame\_buffer](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_release_frame_buffer) | | Releases the frame buffer. Must be called before the end of a layer's `.update_proc` for the layer to be drawn properly. | | c | [graphics\_frame\_buffer\_is\_captured](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_frame_buffer_is_captured) | | Whether or not the frame buffer has been captured by `graphics_capture_frame_buffer`. Graphics functions will not affect the frame buffer until it has been released by `graphics_release_frame_buffer`. | | c | [graphics\_draw\_rotated\_bitmap](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_rotated_bitmap) | | Draws a rotated bitmap with a memory-sensitive 2x anti-aliasing technique (using ray-finding instead of super-sampling), which is thresholded into a b/w bitmap for 1-bit and color blended for 8-bit. | | c | [GOvalScaleMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleMode) | | Values to specify how a given rectangle should be used to derive an oval shape. | | c | [GOvalScaleModeFitCircle](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleModeFitCircle) | | Places a circle at the center of the rectangle, with a diameter that matches the rectangle's shortest side. | | c | [GOvalScaleModeFillCircle](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#GOvalScaleModeFillCircle) | | Places a circle at the center of the rectangle, with a diameter that matches the rectangle's longest side. The circle may overflow the bounds of the rectangle. | | c | [graphics\_draw\_arc](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_draw_arc) | | Draws a line arc clockwise between `angle_start` and `angle_end`, where 0° is the top of the circle. If the difference between `angle_start` and `angle_end` is greater than 360°, a full circle will be drawn. | | c | [graphics\_fill\_radial](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_radial) | | Fills a circle clockwise between `angle_start` and `angle_end`, where 0° is the top of the circle. If the difference between `angle_start` and `angle_end` is greater than 360°, a full circle will be drawn and filled. If `angle_start` is greater than `angle_end` nothing will be drawn. | | c | [gpoint\_from\_polar](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#gpoint_from_polar) | | Calculates a [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) located at the angle provided on the perimeter of a circle defined by the provided [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect). | | c | [grect\_centered\_from\_polar](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#grect_centered_from_polar) | | Calculates a rectangle centered on the perimeter of a circle at a given angle. Use this to construct rectangles that follow the perimeter of a circle as an input for graphics\_fill\_radial\_internal or graphics\_draw\_arc\_internal, e.g. to draw circles every 30 degrees on a watchface. | | c | [Drawing Text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md) | | Functions to draw text into a graphics context | | c | [GTextOverflowMode](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowMode) | | Text overflow mode controls the way text overflows when the string that is drawn does not fit inside the area constraint. | | c | [GTextOverflowModeWordWrap](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowModeWordWrap) | | On overflow, wrap words to a new line below the current one. Once vertical space is consumed, the last line may be clipped. | | c | [GTextOverflowModeTrailingEllipsis](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowModeTrailingEllipsis) | | On overflow, wrap words to a new line below the current one. Once vertical space is consumed, truncate as needed to fit a trailing ellipsis (...). Clipping may occur if the vertical space cannot accomodate the first line of text. | | c | [GTextOverflowModeFill](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextOverflowModeFill) | | Acts like GTextOverflowModeTrailingEllipsis, plus trims leading and trailing newlines, while treating all other newlines as spaces. | | c | [GTextAlignment](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignment) | | Text aligment controls the way the text is aligned inside the box the text is drawn into. | | c | [GTextAlignmentLeft](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignmentLeft) | | Aligns the text to the left of the drawing box. | | c | [GTextAlignmentCenter](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignmentCenter) | | Aligns the text centered inside the drawing box. | | c | [GTextAlignmentRight](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAlignmentRight) | | Aligns the text to the right of the drawing box. | | c | [GTextAttributes](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#GTextAttributes) | | | | c | [graphics\_text\_attributes\_create](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_create) | | Creates an instance of GTextAttributes for advanced control when rendering text. | | c | [graphics\_text\_attributes\_destroy](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_destroy) | | Destroys a previously created instance of GTextAttributes. | | c | [graphics\_text\_attributes\_restore\_default\_text\_flow](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_restore_default_text_flow) | | Restores text flow to the rectangular default. | | c | [graphics\_text\_attributes\_enable\_screen\_text\_flow](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_enable_screen_text_flow) | | Enables text flow that follows the boundaries of the screen. | | c | [graphics\_text\_attributes\_restore\_default\_paging](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_restore_default_paging) | | Restores paging and locked content origin to the defaults. | | c | [graphics\_text\_attributes\_enable\_paging](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_attributes_enable_paging) | | Enables paging and locks the text flow calculation to a fixed point on the screen. | | c | [graphics\_draw\_text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_draw_text) | | Draw text into the current graphics context, using the context's current text color. The text will be drawn inside a box with the specified dimensions and configuration, with clipping occuring automatically. | | c | [graphics\_text\_layout\_get\_content\_size](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_layout_get_content_size) | | Obtain the maximum size that a text with given font, overflow mode and alignment occupies within a given rectangular constraint. | | c | [graphics\_text\_layout\_get\_content\_size\_with\_attributes](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md#graphics_text_layout_get_content_size_with_attributes) | | Obtain the maximum size that a text with given font, overflow mode and alignment occupies within a given rectangular constraint. | | c | [Fonts](https://developer.repebble.com/docs/c/Graphics/Fonts.md) | | Custom and system fonts. | | c | [FontInfo](https://developer.repebble.com/docs/c/Graphics/Fonts.md#FontInfo) | | | | c | [GFont](https://developer.repebble.com/docs/c/Graphics/Fonts.md#GFont) | | Pointer to opaque font data structure. | | c | [fonts\_get\_system\_font](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_get_system_font) | | Loads a system font corresponding to the specified font key. | | c | [fonts\_load\_custom\_font](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_load_custom_font) | | Loads a custom font. | | c | [fonts\_unload\_custom\_font](https://developer.repebble.com/docs/c/Graphics/Fonts.md#fonts_unload_custom_font) | | Unloads the specified custom font and frees the memory that is occupied by it. | | c | [Graphics Context](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md) | | The "canvas" into which an application draws | | c | [graphics\_context\_set\_stroke\_color](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_stroke_color) | | Sets the current stroke color of the graphics context. | | c | [graphics\_context\_set\_fill\_color](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_fill_color) | | Sets the current fill color of the graphics context. | | c | [graphics\_context\_set\_text\_color](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_text_color) | | Sets the current text color of the graphics context. | | c | [graphics\_context\_set\_compositing\_mode](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_compositing_mode) | | Sets the current bitmap compositing mode of the graphics context. The default mode is GCompOpAssign i.e. bitmap transparency disabled. | | c | [graphics\_context\_set\_antialiased](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_antialiased) | | Sets whether antialiasing is applied to stroke drawing. | | c | [graphics\_context\_set\_stroke\_width](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md#graphics_context_set_stroke_width) | | Sets the width of the stroke for drawing routines. | | c | [Graphics Types](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md) | | Basic graphics types (point, rect, size, color, bitmaps, etc.) and utility functions. | | c | [GColorFromRGBA](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColorFromRGBA) | | Convert RGBA to GColor. | | c | [GColorFromRGB](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColorFromRGB) | | Convert RGB to GColor. | | c | [GColorFromHEX](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColorFromHEX) | | Convert hex integer to GColor. | | c | [GColor](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColor) | | | | c | [gcolor\_equal](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gcolor_equal) | | True if both colors are identical or both are invisible (i.e. both have alpha values of .a=0). | | c | [gcolor\_legible\_over](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gcolor_legible_over) | | This method assists in improving the legibility of text on various background colors. It takes the background color for the region in question and computes a color for maximum legibility. | | c | [COLOR\_FALLBACK](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#COLOR_FALLBACK) | | Convenience macro allowing use of a fallback color for black and white platforms. On color platforms, the first expression will be chosen, the second otherwise. | | c | [PBL\_IF\_RECT\_ELSE](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_RECT_ELSE) | | Convenience macro to switch between two expression depending on the screen of the platform. On platforms with rectangular screen, the first expression will be chosen, the second otherwise. | | c | [PBL\_IF\_ROUND\_ELSE](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_ROUND_ELSE) | | Convenience macro to switch between two expression depending on the screen of the platform. On platforms with round screen, the first expression will be chosen, the second otherwise. | | c | [PBL\_IF\_BW\_ELSE](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_BW_ELSE) | | Convenience macro to switch between two expression depending on the screen of the platform. On black& white platforms, the first expression will be chosen, the second otherwise. | | c | [PBL\_IF\_COLOR\_ELSE](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PBL_IF_COLOR_ELSE) | | Convenience macro to switch between two expression depending on the screen of the platform. On color platforms, the first expression will be chosen, the second otherwise. | | c | [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) | | Convenience macro to make a [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint). | | c | [GPointZero](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPointZero) | | Convenience macro to make a [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) at (0, 0). | | c | [gpoint\_equal](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gpoint_equal) | | Tests whether 2 points are equal. | | c | [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) | | Convenience macro to make a [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize). | | c | [GSizeZero](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSizeZero) | | Convenience macro to make a [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) of (0, 0). | | c | [gsize\_equal](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gsize_equal) | | Tests whether 2 sizes are equal. | | c | [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) | | Convenience macro to make a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect). | | c | [GRectZero](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRectZero) | | Convenience macro to make a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) of ((0, 0), (0, 0)). | | c | [grect\_equal](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_equal) | | Tests whether 2 rectangles are equal. | | c | [grect\_is\_empty](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_is_empty) | | Tests whether the size of the rectangle is (0, 0). | | c | [grect\_standardize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_standardize) | | Converts a rectangle's values so that the components of its size (width and/or height) are both positive. In the width and/or height are negative, the origin will offset, so that the final rectangle overlaps with the original. For example, a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) with size (-10, -5) and origin (20, 20), will be standardized to size (10, 5) and origin (10, 15). | | c | [grect\_clip](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_clip) | | Trim one rectangle using the edges of a second rectangle. | | c | [grect\_contains\_point](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_contains_point) | | Tests whether a rectangle contains a point. | | c | [grect\_center\_point](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_center_point) | | Convenience function to compute the center-point of a given rectangle. This is equal to `(rect->x + rect->width / 2, rect->y + rect->height / 2)`. | | c | [grect\_crop](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_crop) | | Reduce the width and height of a rectangle by insetting each of the edges with a fixed inset. The returned rectangle will be centered relative to the input rectangle. | | c | [PLAY\_COUNT\_INFINITE](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PLAY_COUNT_INFINITE) | | Repeat Sequence or animation indefinitely. | | c | [PLAY\_DURATION\_INFINITE](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#PLAY_DURATION_INFINITE) | | Duration of Sequence or animation is infinite. | | c | [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) | | The format of a GBitmap can either be 1-bit or 8-bit. | | c | [GBitmapFormat1Bit](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat1Bit) | | | | c | [GBitmapFormat8Bit](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8Bit) | | | | c | [GBitmapFormat1BitPalette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat1BitPalette) | | | | c | [GBitmapFormat2BitPalette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat2BitPalette) | | | | c | [GBitmapFormat4BitPalette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat4BitPalette) | | | | c | [GBitmapFormat8BitCircular](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat8BitCircular) | | | | c | [GBitmap](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap) | | | | c | [GBitmapSequence](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapSequence) | | | | c | [gbitmap\_get\_bytes\_per\_row](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_bytes_per_row) | | Get the number of bytes per row in the bitmap data for the given GBitmap. On rectangular displays, this can be used as a safe way of iterating over the rows in the bitmap, since bytes per row should be set according to format. On circular displays with pixel format of GBitmapFormat8BitCircular this will return 0, and should not be used for iteration over frame buffer pixels. Instead, use [GBitmapDataRowInfo](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapDataRowInfo), which provides safe minimum and maximum x values for a given row's y value. | | c | [gbitmap\_get\_format](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_format) | | Get the [GBitmapFormat](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat) for the GBitmap. | | c | [gbitmap\_get\_data](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data) | | Get a pointer to the raw image data section of the given GBitmap as specified by the format of the bitmap. | | c | [gbitmap\_set\_data](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_data) | | Set the bitmap data for the given GBitmap. | | c | [gbitmap\_get\_bounds](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_bounds) | | Gets the bounds of the content for the GBitmap. This is set when loading the image or if changed by [gbitmap\_set\_bounds](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_bounds). | | c | [gbitmap\_set\_bounds](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_bounds) | | Set the bounds of the given GBitmap. | | c | [gbitmap\_get\_palette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_palette) | | Get the palette for the given GBitmap. | | c | [gbitmap\_set\_palette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_set_palette) | | Set the palette for the given GBitmap. | | c | [gbitmap\_create\_with\_resource](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_with_resource) | | Creates a new GBitmap on the heap using a Pebble image file stored as a resource. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). | | c | [gbitmap\_create\_with\_data](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_with_data) | | Creates a new GBitmap on the heap initialized with the provided Pebble image data. | | c | [gbitmap\_create\_as\_sub\_bitmap](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_as_sub_bitmap) | | Create a new GBitmap on the heap as a sub-bitmap of a 'base' GBitmap, using a [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) to indicate what portion of the base to use. The sub-bitmap will just reference the image data and palette of the base bitmap. No deep-copying occurs as a result of calling this function, thus the caller is responsible for making sure the base bitmap and palette will remain available when using the sub-bitmap. Note that you should not destroy the parent bitmap until the sub\_bitmap has been destroyed. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). | | c | [gbitmap\_create\_from\_png\_data](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_from_png_data) | | Create a GBitmap based on raw PNG data. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). The developer is responsible for freeing png\_data following this call. | | c | [gbitmap\_create\_blank](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_blank) | | Creates a new blank GBitmap on the heap initialized to zeroes. In the case that the format indicates a palettized bitmap, a palette of appropriate size will also be allocated on the heap. The resulting GBitmap must be destroyed using [gbitmap\_destroy()](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy). | | c | [gbitmap\_create\_blank\_with\_palette](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_blank_with_palette) | | Creates a new blank GBitmap on the heap, initialized to zeroes, and assigns it the given palette. No deep-copying of the palette occurs, so the caller is responsible for making sure the palette remains available when using the resulting bitmap. Management of that memory can be handed off to the system with the free\_on\_destroy argument. | | c | [gbitmap\_create\_palettized\_from\_1bit](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_create_palettized_from_1bit) | | Given a 1-bit GBitmap, create a new bitmap of format GBitmapFormat1BitPalette. The new data buffer is allocated on the heap, and a 2-color palette is allocated as well. | | c | [gbitmap\_destroy](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_destroy) | | Destroy a GBitmap. This must be called for every bitmap that's been created with gbitmap\_create\_\*. | | c | [gbitmap\_sequence\_create\_with\_resource](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_create_with_resource) | | Creates a GBitmapSequence from the specified resource (APNG/PNG files) | | c | [gbitmap\_sequence\_update\_bitmap\_next\_frame](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_update_bitmap_next_frame) | | Updates the contents of the bitmap sequence to the next frame and optionally returns the delay in milliseconds until the next frame. | | c | [gbitmap\_sequence\_update\_bitmap\_by\_elapsed](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_update_bitmap_by_elapsed) | | Updates the contents of the bitmap sequence to the frame at elapsed in the sequence. For looping animations this accounts for the loop, for example an animation of 1 second that is configured to loop 2 times updated to 1500 ms elapsed time will display the sequence frame at 500 ms. Elapsed time is the time from the start of the animation, and will be ignored if it is for a time earlier than the last rendered frame. | | c | [gbitmap\_sequence\_destroy](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_destroy) | | Deletes the GBitmapSequence structure and frees any allocated memory/decoder\_data. | | c | [gbitmap\_sequence\_restart](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_restart) | | Restarts the GBitmapSequence to the first frame [gbitmap\_sequence\_update\_bitmap\_next\_frame](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_update_bitmap_next_frame). | | c | [gbitmap\_sequence\_get\_current\_frame\_idx](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_get_current_frame_idx) | | This function gets the current frame number for the bitmap sequence. | | c | [gbitmap\_sequence\_get\_total\_num\_frames](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_get_total_num_frames) | | This function sets the total number of frames for the bitmap sequence. | | c | [gbitmap\_sequence\_get\_play\_count](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_get_play_count) | | This function gets the play count (number of times to repeat) the bitmap sequence. | | c | [gbitmap\_sequence\_set\_play\_count](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_set_play_count) | | This function sets the play count (number of times to repeat) the bitmap sequence. | | c | [gbitmap\_sequence\_get\_bitmap\_size](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_sequence_get_bitmap_size) | | This function gets the minimum required size (dimensions) necessary to render the bitmap sequence to a GBitmap using the /ref gbitmap\_sequence\_update\_bitmap\_next\_frame. | | c | [gbitmap\_get\_data\_row\_info](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#gbitmap_get_data_row_info) | | Provides information about a pixel data row. | | c | [GAlign](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlign) | | Values to specify how two things should be aligned relative to each other. | | c | [GAlignCenter](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignCenter) | | Align by centering. | | c | [GAlignTopLeft](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignTopLeft) | | Align by making the top edges overlap and left edges overlap. | | c | [GAlignTopRight](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignTopRight) | | Align by making the top edges overlap and left edges overlap. | | c | [GAlignTop](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignTop) | | Align by making the top edges overlap and centered horizontally. | | c | [GAlignLeft](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignLeft) | | Align by making the left edges overlap and centered vertically. | | c | [GAlignBottom](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignBottom) | | Align by making the bottom edges overlap and centered horizontally. | | c | [GAlignRight](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignRight) | | Align by making the right edges overlap and centered vertically. | | c | [GAlignBottomRight](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignBottomRight) | | Align by making the bottom edges overlap and right edges overlap. | | c | [GAlignBottomLeft](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GAlignBottomLeft) | | Align by making the bottom edges overlap and left edges overlap. | | c | [grect\_align](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_align) | | Aligns one rectangle within another rectangle, using an alignment parameter. The relative coordinate systems of both rectangles are assumed to be the same. When clip is true, `rect` is also clipped by the constraint. | | c | [GCompOp](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp) | | Values to specify how the source image should be composited onto the destination image. | | c | [GCompOpAssign](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpAssign) | | Assign the pixel values of the source image to the destination pixels, effectively replacing the previous values for those pixels. For color displays, when drawing a palettized or 8-bit GBitmap image, the opacity value is ignored. | | c | [GCompOpAssignInverted](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpAssignInverted) | | Assign the **inverted** pixel values of the source image to the destination pixels, effectively replacing the previous values for those pixels. | | c | [GCompOpOr](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpOr) | | Use the boolean operator `OR` to composite the source and destination pixels. The visual result of this compositing mode is the source's white pixels are painted onto the destination and the source's black pixels are treated as clear. | | c | [GCompOpAnd](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpAnd) | | Use the boolean operator `AND` to composite the source and destination pixels. The visual result of this compositing mode is the source's black pixels are painted onto the destination and the source's white pixels are treated as clear. | | c | [GCompOpClear](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpClear) | | Clears the bits in the destination image, using the source image as mask. The visual result of this compositing mode is that for the parts where the source image is white, the destination image will be painted black. Other parts will be left untouched. | | c | [GCompOpSet](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpSet) | | Sets the bits in the destination image, using the source image as mask. This mode is required to apply any transparency of your bitmap. | | c | [GContext](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GContext) | | | | c | [GEdgeInsets4](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets4) | | helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro | | c | [GEdgeInsets3](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets3) | | helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro | | c | [GEdgeInsets2](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets2) | | helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro | | c | [GEdgeInsets1](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets1) | | helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro | | c | [GEdgeInsetsN](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsetsN) | | helper for [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) macro | | c | [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) | | Convenience macro to make a [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) This macro follows the CSS shorthand notation where you can call it with. | | c | [grect\_inset](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#grect_inset) | | Returns a rectangle that is shrinked or expanded by the given edge insets. | | c | [GBitmapDataRowInfo](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapDataRowInfo) | | Description of a single data row in the pixel data of a bitmap. | | c | [GColor8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GColor8) | | | | c | [GEdgeInsets](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GEdgeInsets) | | Represents insets for four sides. Negative values mean a side extends. | | c | [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) | | Represents a point in a 2-dimensional coordinate system. | | c | [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) | | Represents a rectangle and defining it using the origin of the upper-lefthand corner and its size. | | c | [GSize](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GSize) | | Represents a 2-dimensional size. | | c | [Color Definitions](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md) | | A list of all of the named colors available with links to the color map on the Pebble Developer website. | | c | [GColorBlackARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlackARGB8) | | | | c | [GColorOxfordBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorOxfordBlueARGB8) | | | | c | [GColorDukeBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDukeBlueARGB8) | | | | c | [GColorBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlueARGB8) | | | | c | [GColorDarkGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDarkGreenARGB8) | | | | c | [GColorMidnightGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMidnightGreenARGB8) | | | | c | [GColorCobaltBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCobaltBlueARGB8) | | | | c | [GColorBlueMoonARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlueMoonARGB8) | | | | c | [GColorIslamicGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorIslamicGreenARGB8) | | | | c | [GColorJaegerGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorJaegerGreenARGB8) | | | | c | [GColorTiffanyBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorTiffanyBlueARGB8) | | | | c | [GColorVividCeruleanARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorVividCeruleanARGB8) | | | | c | [GColorGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorGreenARGB8) | | | | c | [GColorMalachiteARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMalachiteARGB8) | | | | c | [GColorMediumSpringGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMediumSpringGreenARGB8) | | | | c | [GColorCyanARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCyanARGB8) | | | | c | [GColorBulgarianRoseARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBulgarianRoseARGB8) | | | | c | [GColorImperialPurpleARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorImperialPurpleARGB8) | | | | c | [GColorIndigoARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorIndigoARGB8) | | | | c | [GColorElectricUltramarineARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorElectricUltramarineARGB8) | | | | c | [GColorArmyGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorArmyGreenARGB8) | | | | c | [GColorDarkGrayARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDarkGrayARGB8) | | | | c | [GColorLibertyARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLibertyARGB8) | | | | c | [GColorVeryLightBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorVeryLightBlueARGB8) | | | | c | [GColorKellyGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorKellyGreenARGB8) | | | | c | [GColorMayGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMayGreenARGB8) | | | | c | [GColorCadetBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCadetBlueARGB8) | | | | c | [GColorPictonBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPictonBlueARGB8) | | | | c | [GColorBrightGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBrightGreenARGB8) | | | | c | [GColorScreaminGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorScreaminGreenARGB8) | | | | c | [GColorMediumAquamarineARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMediumAquamarineARGB8) | | | | c | [GColorElectricBlueARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorElectricBlueARGB8) | | | | c | [GColorDarkCandyAppleRedARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDarkCandyAppleRedARGB8) | | | | c | [GColorJazzberryJamARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorJazzberryJamARGB8) | | | | c | [GColorPurpleARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPurpleARGB8) | | | | c | [GColorVividVioletARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorVividVioletARGB8) | | | | c | [GColorWindsorTanARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorWindsorTanARGB8) | | | | c | [GColorRoseValeARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRoseValeARGB8) | | | | c | [GColorPurpureusARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPurpureusARGB8) | | | | c | [GColorLavenderIndigoARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLavenderIndigoARGB8) | | | | c | [GColorLimerickARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLimerickARGB8) | | | | c | [GColorBrassARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBrassARGB8) | | | | c | [GColorLightGrayARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLightGrayARGB8) | | | | c | [GColorBabyBlueEyesARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBabyBlueEyesARGB8) | | | | c | [GColorSpringBudARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorSpringBudARGB8) | | | | c | [GColorInchwormARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorInchwormARGB8) | | | | c | [GColorMintGreenARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMintGreenARGB8) | | | | c | [GColorCelesteARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCelesteARGB8) | | | | c | [GColorRedARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRedARGB8) | | | | c | [GColorFollyARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorFollyARGB8) | | | | c | [GColorFashionMagentaARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorFashionMagentaARGB8) | | | | c | [GColorMagentaARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMagentaARGB8) | | | | c | [GColorOrangeARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorOrangeARGB8) | | | | c | [GColorSunsetOrangeARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorSunsetOrangeARGB8) | | | | c | [GColorBrilliantRoseARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBrilliantRoseARGB8) | | | | c | [GColorShockingPinkARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorShockingPinkARGB8) | | | | c | [GColorChromeYellowARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorChromeYellowARGB8) | | | | c | [GColorRajahARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRajahARGB8) | | | | c | [GColorMelonARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMelonARGB8) | | | | c | [GColorRichBrilliantLavenderARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRichBrilliantLavenderARGB8) | | | | c | [GColorYellowARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorYellowARGB8) | | | | c | [GColorIcterineARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorIcterineARGB8) | | | | c | [GColorPastelYellowARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPastelYellowARGB8) | | | | c | [GColorWhiteARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorWhiteARGB8) | | | | c | [GColorBlack](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlack) | | [GColorBlack](https://developer.repebble.com/tools/color-picker/#000000) | | c | [GColorOxfordBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorOxfordBlue) | | [GColorOxfordBlue](https://developer.repebble.com/tools/color-picker/#000055) | | c | [GColorDukeBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDukeBlue) | | [GColorDukeBlue](https://developer.repebble.com/tools/color-picker/#0000AA) | | c | [GColorBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlue) | | [GColorBlue](https://developer.repebble.com/tools/color-picker/#0000FF) | | c | [GColorDarkGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDarkGreen) | | [GColorDarkGreen](https://developer.repebble.com/tools/color-picker/#005500) | | c | [GColorMidnightGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMidnightGreen) | | [GColorMidnightGreen](https://developer.repebble.com/tools/color-picker/#005555) | | c | [GColorCobaltBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCobaltBlue) | | [GColorCobaltBlue](https://developer.repebble.com/tools/color-picker/#0055AA) | | c | [GColorBlueMoon](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlueMoon) | | [GColorBlueMoon](https://developer.repebble.com/tools/color-picker/#0055FF) | | c | [GColorIslamicGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorIslamicGreen) | | [GColorIslamicGreen](https://developer.repebble.com/tools/color-picker/#00AA00) | | c | [GColorJaegerGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorJaegerGreen) | | [GColorJaegerGreen](https://developer.repebble.com/tools/color-picker/#00AA55) | | c | [GColorTiffanyBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorTiffanyBlue) | | [GColorTiffanyBlue](https://developer.repebble.com/tools/color-picker/#00AAAA) | | c | [GColorVividCerulean](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorVividCerulean) | | [GColorVividCerulean](https://developer.repebble.com/tools/color-picker/#00AAFF) | | c | [GColorGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorGreen) | | [GColorGreen](https://developer.repebble.com/tools/color-picker/#00FF00) | | c | [GColorMalachite](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMalachite) | | [GColorMalachite](https://developer.repebble.com/tools/color-picker/#00FF55) | | c | [GColorMediumSpringGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMediumSpringGreen) | | [GColorMediumSpringGreen](https://developer.repebble.com/tools/color-picker/#00FFAA) | | c | [GColorCyan](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCyan) | | [GColorCyan](https://developer.repebble.com/tools/color-picker/#00FFFF) | | c | [GColorBulgarianRose](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBulgarianRose) | | [GColorBulgarianRose](https://developer.repebble.com/tools/color-picker/#550000) | | c | [GColorImperialPurple](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorImperialPurple) | | [GColorImperialPurple](https://developer.repebble.com/tools/color-picker/#550055) | | c | [GColorIndigo](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorIndigo) | | [GColorIndigo](https://developer.repebble.com/tools/color-picker/#5500AA) | | c | [GColorElectricUltramarine](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorElectricUltramarine) | | [GColorElectricUltramarine](https://developer.repebble.com/tools/color-picker/#5500FF) | | c | [GColorArmyGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorArmyGreen) | | [GColorArmyGreen](https://developer.repebble.com/tools/color-picker/#555500) | | c | [GColorDarkGray](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDarkGray) | | [GColorDarkGray](https://developer.repebble.com/tools/color-picker/#555555) | | c | [GColorLiberty](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLiberty) | | [GColorLiberty](https://developer.repebble.com/tools/color-picker/#5555AA) | | c | [GColorVeryLightBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorVeryLightBlue) | | [GColorVeryLightBlue](https://developer.repebble.com/tools/color-picker/#5555FF) | | c | [GColorKellyGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorKellyGreen) | | [GColorKellyGreen](https://developer.repebble.com/tools/color-picker/#55AA00) | | c | [GColorMayGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMayGreen) | | [GColorMayGreen](https://developer.repebble.com/tools/color-picker/#55AA55) | | c | [GColorCadetBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCadetBlue) | | [GColorCadetBlue](https://developer.repebble.com/tools/color-picker/#55AAAA) | | c | [GColorPictonBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPictonBlue) | | [GColorPictonBlue](https://developer.repebble.com/tools/color-picker/#55AAFF) | | c | [GColorBrightGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBrightGreen) | | [GColorBrightGreen](https://developer.repebble.com/tools/color-picker/#55FF00) | | c | [GColorScreaminGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorScreaminGreen) | | [GColorScreaminGreen](https://developer.repebble.com/tools/color-picker/#55FF55) | | c | [GColorMediumAquamarine](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMediumAquamarine) | | [GColorMediumAquamarine](https://developer.repebble.com/tools/color-picker/#55FFAA) | | c | [GColorElectricBlue](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorElectricBlue) | | [GColorElectricBlue](https://developer.repebble.com/tools/color-picker/#55FFFF) | | c | [GColorDarkCandyAppleRed](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorDarkCandyAppleRed) | | [GColorDarkCandyAppleRed](https://developer.repebble.com/tools/color-picker/#AA0000) | | c | [GColorJazzberryJam](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorJazzberryJam) | | [GColorJazzberryJam](https://developer.repebble.com/tools/color-picker/#AA0055) | | c | [GColorPurple](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPurple) | | [GColorPurple](https://developer.repebble.com/tools/color-picker/#AA00AA) | | c | [GColorVividViolet](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorVividViolet) | | [GColorVividViolet](https://developer.repebble.com/tools/color-picker/#AA00FF) | | c | [GColorWindsorTan](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorWindsorTan) | | [GColorWindsorTan](https://developer.repebble.com/tools/color-picker/#AA5500) | | c | [GColorRoseVale](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRoseVale) | | [GColorRoseVale](https://developer.repebble.com/tools/color-picker/#AA5555) | | c | [GColorPurpureus](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPurpureus) | | [GColorPurpureus](https://developer.repebble.com/tools/color-picker/#AA55AA) | | c | [GColorLavenderIndigo](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLavenderIndigo) | | [GColorLavenderIndigo](https://developer.repebble.com/tools/color-picker/#AA55FF) | | c | [GColorLimerick](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLimerick) | | [GColorLimerick](https://developer.repebble.com/tools/color-picker/#AAAA00) | | c | [GColorBrass](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBrass) | | [GColorBrass](https://developer.repebble.com/tools/color-picker/#AAAA55) | | c | [GColorLightGray](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorLightGray) | | [GColorLightGray](https://developer.repebble.com/tools/color-picker/#AAAAAA) | | c | [GColorBabyBlueEyes](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBabyBlueEyes) | | [GColorBabyBlueEyes](https://developer.repebble.com/tools/color-picker/#AAAAFF) | | c | [GColorSpringBud](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorSpringBud) | | [GColorSpringBud](https://developer.repebble.com/tools/color-picker/#AAFF00) | | c | [GColorInchworm](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorInchworm) | | [GColorInchworm](https://developer.repebble.com/tools/color-picker/#AAFF55) | | c | [GColorMintGreen](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMintGreen) | | [GColorMintGreen](https://developer.repebble.com/tools/color-picker/#AAFFAA) | | c | [GColorCeleste](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorCeleste) | | [GColorCeleste](https://developer.repebble.com/tools/color-picker/#AAFFFF) | | c | [GColorRed](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRed) | | [GColorRed](https://developer.repebble.com/tools/color-picker/#FF0000) | | c | [GColorFolly](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorFolly) | | [GColorFolly](https://developer.repebble.com/tools/color-picker/#FF0055) | | c | [GColorFashionMagenta](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorFashionMagenta) | | [GColorFashionMagenta](https://developer.repebble.com/tools/color-picker/#FF00AA) | | c | [GColorMagenta](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMagenta) | | [GColorMagenta](https://developer.repebble.com/tools/color-picker/#FF00FF) | | c | [GColorOrange](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorOrange) | | [GColorOrange](https://developer.repebble.com/tools/color-picker/#FF5500) | | c | [GColorSunsetOrange](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorSunsetOrange) | | [GColorSunsetOrange](https://developer.repebble.com/tools/color-picker/#FF5555) | | c | [GColorBrilliantRose](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBrilliantRose) | | [GColorBrilliantRose](https://developer.repebble.com/tools/color-picker/#FF55AA) | | c | [GColorShockingPink](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorShockingPink) | | [GColorShockingPink](https://developer.repebble.com/tools/color-picker/#FF55FF) | | c | [GColorChromeYellow](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorChromeYellow) | | [GColorChromeYellow](https://developer.repebble.com/tools/color-picker/#FFAA00) | | c | [GColorRajah](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRajah) | | [GColorRajah](https://developer.repebble.com/tools/color-picker/#FFAA55) | | c | [GColorMelon](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorMelon) | | [GColorMelon](https://developer.repebble.com/tools/color-picker/#FFAAAA) | | c | [GColorRichBrilliantLavender](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorRichBrilliantLavender) | | [GColorRichBrilliantLavender](https://developer.repebble.com/tools/color-picker/#FFAAFF) | | c | [GColorYellow](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorYellow) | | [GColorYellow](https://developer.repebble.com/tools/color-picker/#FFFF00) | | c | [GColorIcterine](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorIcterine) | | [GColorIcterine](https://developer.repebble.com/tools/color-picker/#FFFF55) | | c | [GColorPastelYellow](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorPastelYellow) | | [GColorPastelYellow](https://developer.repebble.com/tools/color-picker/#FFFFAA) | | c | [GColorWhite](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorWhite) | | [GColorWhite](https://developer.repebble.com/tools/color-picker/#FFFFFF) | | c | [GColorClearARGB8](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorClearARGB8) | | | | c | [GColorClear](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorClear) | | | | c | [User Interface](https://developer.repebble.com/docs/c/User_Interface.md) | | Everything related to user interface. | | c | [Animation](https://developer.repebble.com/docs/c/User_Interface/Animation.md) | | Abstract framework to create arbitrary animations | | c | [Animation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#Animation) | | | | c | [AnimationProgress](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationProgress) | | The type used to represent how far an animation has progressed. This is passed to the animation's update handler. | | c | [AnimationCurve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve) | | Values that are used to indicate the different animation curves, which determine the speed at which the animated value(s) change(s). | | c | [AnimationCurveLinear](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveLinear) | | Linear curve: the velocity is constant. | | c | [AnimationCurveEaseIn](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveEaseIn) | | Bicubic ease-in: accelerate from zero velocity. | | c | [AnimationCurveEaseOut](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveEaseOut) | | Bicubic ease-in: decelerate to zero velocity. | | c | [AnimationCurveEaseInOut](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveEaseInOut) | | Bicubic ease-in-out: accelerate from zero velocity, decelerate to zero velocity. | | c | [AnimationCurveDefault](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveDefault) | | | | c | [AnimationCurveCustomFunction](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveCustomFunction) | | Custom (user-provided) animation curve. | | c | [AnimationCurveCustomInterpolationFunction](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveCustomInterpolationFunction) | | User-provided interpolation function. | | c | [AnimationCurve\_Reserved1](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve_Reserved1) | | | | c | [AnimationCurve\_Reserved2](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurve_Reserved2) | | | | c | [animation\_create](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create) | | Creates a new Animation on the heap and initalizes it with the default values. | | c | [animation\_destroy](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_destroy) | | Destroys an Animation previously created by animation\_create. | | c | [ANIMATION\_DURATION\_INFINITE](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_DURATION_INFINITE) | | Constant to indicate "infinite" duration. This can be used with [animation\_set\_duration()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_duration) to indicate that the animation should run indefinitely. This is useful when implementing for example a frame-by-frame simulation that does not have a clear ending (e.g. a game). | | c | [ANIMATION\_PLAY\_COUNT\_INFINITE](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_PLAY_COUNT_INFINITE) | | Constant to indicate infinite play count. Can be passed to [animation\_set\_play\_count()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_play_count) to repeat indefinitely. | | c | [ANIMATION\_NORMALIZED\_MIN](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MIN) | | The normalized distance at the start of the animation. | | c | [ANIMATION\_NORMALIZED\_MAX](https://developer.repebble.com/docs/c/User_Interface/Animation.md#ANIMATION_NORMALIZED_MAX) | | The normalized distance at the end of the animation. | | c | [animation\_clone](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_clone) | | | | c | [animation\_sequence\_create](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_sequence_create) | | Create a new sequence animation from a list of 2 or more other animations. The returned animation owns the animations that were provided as arguments and no further write operations on those handles are allowed. The variable length argument list must be terminated with a NULL ptr. | | c | [animation\_sequence\_create\_from\_array](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_sequence_create_from_array) | | An alternate form of [animation\_sequence\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_sequence_create) that accepts an array of other animations. | | c | [animation\_spawn\_create](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_spawn_create) | | Create a new spawn animation from a list of 2 or more other animations. The returned animation owns the animations that were provided as arguments and no further write operations on those handles are allowed. The variable length argument list must be terminated with a NULL ptr. | | c | [animation\_spawn\_create\_from\_array](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_spawn_create_from_array) | | An alternate form of [animation\_spawn\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_spawn_create) that accepts an array of other animations. | | c | [animation\_set\_elapsed](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_elapsed) | | Seek to a specific location in the animation. Only forward seeking is allowed. Returns true if successful, false if the passed in seek location is invalid. | | c | [animation\_get\_elapsed](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_elapsed) | | Get the current location in the animation. | | c | [animation\_set\_reverse](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_reverse) | | Set an animation to run in reverse (or forward) | | c | [animation\_get\_reverse](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_reverse) | | Get the reverse setting of an animation. | | c | [animation\_set\_play\_count](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_play_count) | | Set an animation to play N times. The default is 1. | | c | [animation\_get\_play\_count](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_play_count) | | Get the play count of an animation. | | c | [animation\_set\_duration](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_duration) | | Sets the time in milliseconds that an animation takes from start to finish. | | c | [animation\_get\_duration](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_duration) | | Get the static duration of an animation from start to end (ignoring how much has already played, if any). | | c | [animation\_set\_delay](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_delay) | | Sets an optional delay for the animation. | | c | [animation\_get\_delay](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_delay) | | Get the delay of an animation in milliseconds. | | c | [animation\_set\_curve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_curve) | | Sets the animation curve for the animation. | | c | [animation\_get\_curve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_curve) | | Gets the animation curve for the animation. | | c | [AnimationCurveFunction](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationCurveFunction) | | The function pointer type of a custom animation curve. | | c | [animation\_set\_custom\_curve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_custom_curve) | | Sets a custom animation curve function. | | c | [animation\_get\_custom\_curve](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_custom_curve) | | Gets the custom animation curve function for the animation. | | c | [AnimationStartedHandler](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationStartedHandler) | | The function pointer type of the handler that will be called when an animation is started, just before updating the first frame of the animation. | | c | [AnimationStoppedHandler](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationStoppedHandler) | | The function pointer type of the handler that will be called when the animation is stopped. | | c | [animation\_set\_handlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers) | | Sets the callbacks for the animation. Often an application needs to run code at the start or at the end of an animation. Using this function is possible to register callback functions with an animation, that will get called at the start and end of the animation. | | c | [animation\_get\_context](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_context) | | Gets the application-specific callback context of the animation. This `void` pointer is passed as an argument when the animation system calls [AnimationHandlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationHandlers) callbacks. The context pointer can be set to point to any application specific data using [animation\_set\_handlers()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_handlers). | | c | [animation\_schedule](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_schedule) | | Schedules the animation. Call this once after configuring an animation to get it to start running. | | c | [animation\_unschedule](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_unschedule) | | Unschedules the animation, which in effect stops the animation. | | c | [animation\_unschedule\_all](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_unschedule_all) | | Unschedules all animations of the application. | | c | [animation\_is\_scheduled](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_is_scheduled) | | | | c | [AnimationSetupImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationSetupImplementation) | | Pointer to function that (optionally) prepares the animation for running. This callback is called when the animation is added to the scheduler. | | c | [AnimationUpdateImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationUpdateImplementation) | | Pointer to function that updates the animation according to the given normalized progress. This callback will be called repeatedly by the animation scheduler whenever the animation needs to be updated. | | c | [AnimationTeardownImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationTeardownImplementation) | | Pointer to function that (optionally) cleans up the animation. This callback is called when the animation is removed from the scheduler. In case the `.setup` implementation allocated any memory, this is a good place to release that memory again. | | c | [animation\_set\_implementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_set_implementation) | | Sets the implementation of the custom animation. When implementing custom animations, use this function to specify what functions need to be called to for the setup, frame update and teardown of the animation. | | c | [animation\_get\_implementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_get_implementation) | | Gets the implementation of the custom animation. | | c | [AnimationHandlers](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationHandlers) | | The handlers that will get called when an animation starts and stops. See documentation with the function pointer types for more information. | | c | [AnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation.md#AnimationImplementation) | | The 3 callbacks that implement a custom animation. Only the `.update` callback is mandatory, `.setup` and `.teardown` are optional. See the documentation with the function pointer typedefs for more information. | | c | [PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md) | | A ProperyAnimation animates the value of a "property" of a "subject" over time. | | c | [PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimation) | | | | c | [property\_animation\_create\_layer\_frame](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create_layer_frame) | | Convenience function to create and initialize a property animation that animates the frame of a Layer. It sets up the PropertyAnimation to use [layer\_set\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) and [layer\_get\_frame()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_frame) as accessors and uses the `layer` parameter as the subject for the animation. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). | | c | [property\_animation\_create\_bounds\_origin](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create_bounds_origin) | | Convenience function to create and initialize a property animation that animates the bound's origin of a Layer. It sets up the PropertyAnimation to use [layer\_set\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) and [layer\_get\_bounds()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds) as accessors and uses the `layer` parameter as the subject for the animation. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). | | c | [property\_animation\_create](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) | | Creates a new PropertyAnimation on the heap and and initializes it with the specified values. The same defaults are used as with [animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation.md#animation_create). If the `from_value` or the `to_value` is `NULL`, the getter accessor will be called to get the current value of the property and be used instead. | | c | [property\_animation\_destroy](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_destroy) | | Destroy a property animation allocated by [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create) or relatives. | | c | [property\_animation\_update\_int16](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16) | | Default update callback for a property animations to update a property of type int16\_t. Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [Int16Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Getter) and [Int16Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Setter). The implementation of this function will calculate the next value of the animation and call the setter to set the new value upon the subject. | | c | [property\_animation\_update\_uint32](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_uint32) | | Default update callback for a property animations to update a property of type [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t). Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [UInt32Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#UInt32Getter) and [UInt32Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#UInt32Setter). The implementation of this function will calculate the next value of the animation and call the setter to set the new value upon the subject. | | c | [property\_animation\_update\_gpoint](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gpoint) | | Default update callback for a property animations to update a property of type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint). Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [GPointGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointGetter) and [GPointSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointSetter). The implementation of this function will calculate the next point of the animation and call the setter to set the new point upon the subject. | | c | [property\_animation\_update\_grect](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_grect) | | Default update callback for a property animations to update a property of type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect). Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [GRectGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectGetter) and [GRectSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectSetter). The implementation of this function will calculate the next rectangle of the animation and call the setter to set the new rectangle upon the subject. | | c | [property\_animation\_update\_gcolor8](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gcolor8) | | Default update callback for a property animations to update a property of type GColor8. Assign this function to the `.base.update` callback field of your [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation), in combination with a `.getter` and `.setter` accessors of types [GColor8Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GColor8Getter) and [GColor8Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GColor8Setter). The implementation of this function will calculate the next rectangle of the animation and call the setter to set the new value upon the subject. | | c | [GPointReturn](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointReturn) | | Work-around for function pointer return type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) to avoid tripping the pre-processor to use the equally named [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) define. | | c | [GRectReturn](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectReturn) | | Work-around for function pointer return type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) to avoid tripping the pre-processor to use the equally named [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) define. | | c | [Int16Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Setter) | | Function signature of a setter function to set a property of type int16\_t onto the subject. | | c | [Int16Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#Int16Getter) | | Function signature of a getter function to get the current property of type int16\_t of the subject. | | c | [UInt32Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#UInt32Setter) | | Function signature of a setter function to set a property of type [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) onto the subject. | | c | [UInt32Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#UInt32Getter) | | Function signature of a getter function to get the current property of type [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) of the subject. | | c | [GPointSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointSetter) | | Function signature of a setter function to set a property of type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) onto the subject. | | c | [GPointGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GPointGetter) | | Function signature of a getter function to get the current property of type [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) of the subject. | | c | [GRectSetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectSetter) | | Function signature of a setter function to set a property of type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) onto the subject. | | c | [GRectGetter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GRectGetter) | | Function signature of a getter function to get the current property of type [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) of the subject. | | c | [GColor8Setter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GColor8Setter) | | Function signature of a setter function to set a property of type GColor8 onto the subject. | | c | [GColor8Getter](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#GColor8Getter) | | Function signature of a getter function to get the current property of type GColor8 of the subject. | | c | [property\_animation\_get\_animation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_animation) | | Convenience function to retrieve an animation instance from a property animation instance. | | c | [property\_animation\_clone](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_clone) | | Convenience function to clone a property animation instance. | | c | [property\_animation\_get\_from\_grect](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_from_grect) | | Convenience function to retrieve the 'from' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value from property animation handle. | | c | [property\_animation\_set\_from\_grect](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_set_from_grect) | | Convenience function to set the 'from' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value of property animation handle. | | c | [property\_animation\_get\_from\_gpoint](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_from_gpoint) | | Convenience function to retrieve the 'from' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value from property animation handle. | | c | [property\_animation\_set\_from\_gpoint](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_set_from_gpoint) | | Convenience function to set the 'from' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value of property animation handle. | | c | [property\_animation\_get\_from\_int16](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_from_int16) | | Convenience function to retrieve the 'from' int16\_t value from property animation handle. | | c | [property\_animation\_set\_from\_int16](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_set_from_int16) | | Convenience function to set the 'from' int16\_t value of property animation handle. | | c | [property\_animation\_get\_to\_grect](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_to_grect) | | Convenience function to retrieve the 'to' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value from property animation handle. | | c | [property\_animation\_set\_to\_grect](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_set_to_grect) | | Convenience function to set the 'to' [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect) value of property animation handle. | | c | [property\_animation\_get\_to\_gpoint](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_to_gpoint) | | Convenience function to retrieve the 'to' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value from property animation handle. | | c | [property\_animation\_set\_to\_gpoint](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_set_to_gpoint) | | Convenience function to set the 'to' [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) value of property animation handle. | | c | [property\_animation\_get\_to\_int16](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_to_int16) | | Convenience function to retrieve the 'to' int16\_t value from property animation handle. | | c | [property\_animation\_set\_to\_int16](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_set_to_int16) | | Convenience function to set the 'to' int16\_t value of property animation handle. | | c | [property\_animation\_get\_subject](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_get_subject) | | Retrieve the subject of a property animation. | | c | [property\_animation\_set\_subject](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_set_subject) | | Set the subject of a property animation. | | c | [property\_animation\_subject](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_subject) | | Helper function used by the property\_animation\_get|set\_subject macros. | | c | [property\_animation\_from](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_from) | | Helper function used by the property\_animation\_get|set\_from\_.\* macros. | | c | [property\_animation\_to](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_to) | | Helper function used by the property\_animation\_get|set\_to\_.\* macros. | | c | [PropertyAnimationAccessors](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationAccessors) | | Data structure containing the setter and getter function pointers that the property animation should use. The specified setter function will be used by the animation's update callback. Based on the type of the property (int16\_t, [GPoint](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GPoint) or [GRect](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect)), the accompanying update callback should be used, see [property\_animation\_update\_int16()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_int16), [property\_animation\_update\_gpoint()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_gpoint) and [property\_animation\_update\_grect()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_update_grect). The getter function is used when the animation is initialized, to assign the current value of the subject's property as "from" or "to" value, see [property\_animation\_create()](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#property_animation_create). | | c | [PropertyAnimationImplementation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md#PropertyAnimationImplementation) | | Data structure containing a collection of function pointers that form the implementation of the property animation. See the code example at the top ([PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md)). | | c | [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) | | Handling button click interactions | | c | [ButtonId](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ButtonId) | | Button ID values. | | c | [BUTTON\_ID\_BACK](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#BUTTON_ID_BACK) | | Back button. | | c | [BUTTON\_ID\_UP](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#BUTTON_ID_UP) | | Up button. | | c | [BUTTON\_ID\_SELECT](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#BUTTON_ID_SELECT) | | Select (middle) button. | | c | [BUTTON\_ID\_DOWN](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#BUTTON_ID_DOWN) | | Down button. | | c | [NUM\_BUTTONS](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#NUM_BUTTONS) | | Total number of buttons. | | c | [ClickRecognizerRef](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickRecognizerRef) | | Reference to opaque click recognizer When a [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) callback is called, the recognizer that fired the handler is passed in. | | c | [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) | | Function signature of the callback that handles a recognized click pattern. | | c | [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) | | This callback is called every time the window becomes visible (and when you call [window\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider) if the window is already visible). | | c | [click\_number\_of\_clicks\_counted](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_number_of_clicks_counted) | | Gets the click count. You can use this inside a click handler implementation to get the click count for multi\_click and (repeated) click events. | | c | [click\_recognizer\_get\_button\_id](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_recognizer_get_button_id) | | Gets the button identifier. You can use this inside a click handler implementation to get the button id for the click event. | | c | [click\_recognizer\_is\_repeating](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#click_recognizer_is_repeating) | | Is this a repeating click. You can use this inside a click handler implementation to find out whether this is a repeating click or not. | | c | [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) | | Low-level touch-gesture recognizers for building fully custom touch interactions on touch-capable hardware. | | c | [Recognizer](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#Recognizer) | | | | c | [RecognizerEvent](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent) | | | | c | [RecognizerEvent\_Started](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Started) | | | | c | [RecognizerEvent\_Updated](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Updated) | | | | c | [RecognizerEvent\_Completed](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Completed) | | | | c | [RecognizerEvent\_Cancelled](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Cancelled) | | | | c | [RecognizerEventCb](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEventCb) | | User event callback. When a recognizer changes state to any state other than the failed state the user callback of this type will be called. | | c | [RecognizerSimultaneousWithCb](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerSimultaneousWithCb) | | This function is called to determine whether _recognizer_ should be evaluated simultaneously with _simultaneous\_with_. | | c | [PanAxis](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis) | | Axis to which a pan recognizer is locked. A pan is only recognized when the finger moves unambiguously along this axis. | | c | [PanAxis\_Horizontal](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis_Horizontal) | | | | c | [PanAxis\_Vertical](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis_Vertical) | | | | c | [SwipeDirection](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection) | | Swipe direction, also used as a bitmask when configuring which directions a swipe recognizer accepts. Screen coordinates grow downward, so a positive y delta is a downward swipe. | | c | [SwipeDirection\_None](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection_None) | | | | c | [SwipeDirection\_Up](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection_Up) | | | | c | [SwipeDirection\_Down](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection_Down) | | | | c | [SwipeDirection\_Left](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection_Left) | | | | c | [SwipeDirection\_Right](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection_Right) | | | | c | [tap\_recognizer\_create](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_create) | | Create a tap recognizer. The default recognizer recognizes a single tap from a single finger. | | c | [pan\_recognizer\_create](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_create) | | Create a pan recognizer locked to a single axis. The recognizer stays in the Possible state until the finger moves unambiguously along _axis_ and crosses the start threshold, at which point it Starts. It Fails if the finger instead moves unambiguously along the foreign axis. | | c | [swipe\_recognizer\_create](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_create) | | Create a swipe recognizer that accepts the directions set in _direction\_mask_. The recognizer stays Possible while tracking the path and Completes on liftoff if the path is a fast, straight, long-enough flick whose direction is in the mask; otherwise it Fails. | | c | [tap\_recognizer\_get\_tap\_point](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_get_tap_point) | | Get the coordinate of the recognized tap. The coordinate is taken from the last position update, not the liftoff. Valid once the recognizer has completed. | | c | [pan\_recognizer\_get\_total\_delta](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_total_delta) | | Get the total movement of the pan since the touchdown point. Component-wise. | | c | [pan\_recognizer\_get\_delta\_since\_start](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_start) | | Get the movement of the pan since it Started (i.e. since the start threshold was crossed). This is exactly (0, 0) at the instant the recognizer transitions to Started, so live scroll that consumes this value does not jump at gesture start. Component-wise. | | c | [pan\_recognizer\_get\_delta\_since\_prev](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_prev) | | Get the movement of the pan since the previous position update. Component-wise. | | c | [pan\_recognizer\_get\_velocity](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_velocity) | | Get the current velocity of the pan, in pixels per second, component-wise. Computed over the most-recent events within a short time window (see PAN\_VELOCITY\_WINDOW\_MS). Zero when the elapsed time across the sampled events is zero. | | c | [swipe\_recognizer\_get\_direction](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_direction) | | Get the recognized swipe direction. Valid once the recognizer has Completed; otherwise SwipeDirection\_None. | | c | [swipe\_recognizer\_get\_velocity](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_velocity) | | Get the velocity of the swipe, in pixels per second, component-wise. Computed over the most-recent events within a short time window. Zero when the elapsed time is zero. | | c | [recognizer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_destroy) | | Destroy an un-owned recognizer. If a recognizer is not owned, this will destroy the recognizer freeing it's data and calling the destructor (see recognizer\_set\_on\_destroy), if set. If it is owned, this will do nothing. | | c | [recognizer\_set\_simultaneous\_with](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_simultaneous_with) | | Specify a callback to determine whether a recognizer should be evaluated simultaneously with another recognizer. | | c | [recognizer\_set\_fail\_after](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_fail_after) | | Tell a recognizer to only evaluate after the another recognizer fails. | | c | [Layers](https://developer.repebble.com/docs/c/User_Interface/Layers.md) | | User interface layers for displaying graphic components | | c | [Layer](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer) | | | | c | [LayerUpdateProc](https://developer.repebble.com/docs/c/User_Interface/Layers.md#LayerUpdateProc) | | Function signature for a Layer's render callback (the name of the type is derived from the words 'update procedure'). The system will call the `.update_proc` callback whenever the Layer needs to be rendered. | | c | [layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_create) | | Creates a layer on the heap and sets its frame and bounds. Default values: | | c | [layer\_create\_with\_data](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_create_with_data) | | Creates a layer on the heap with extra space for callback data, and set its frame andbounds. Default values: | | c | [layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_destroy) | | Destroys a layer previously created by layer\_create. | | c | [layer\_mark\_dirty](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) | | Marks the complete layer as "dirty", awaiting to be asked by the system to redraw itself. Typically, this function is called whenever state has changed that affects what the layer is displaying. | | c | [layer\_set\_update\_proc](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_update_proc) | | Sets the layer's render function. The system will call the `update_proc` automatically when the layer needs to redraw itself, see also [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty). | | c | [layer\_set\_frame](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_frame) | | Sets the frame of the layer, which is it's bounding box relative to the coordinate system of its parent layer. The size of the layer's bounds will be extended automatically, so that the bounds cover the new frame. | | c | [layer\_get\_frame](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_frame) | | Gets the frame of the layer, which is it's bounding box relative to the coordinate system of its parent layer. If the frame has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically. | | c | [layer\_set\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_bounds) | | Sets the bounds of the layer, which is it's bounding box relative to its frame. If the bounds has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically. | | c | [layer\_get\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_bounds) | | Gets the bounds of the layer. | | c | [layer\_get\_unobstructed\_bounds](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_unobstructed_bounds) | | Get the largest unobstructed bounds rectangle of a layer. | | c | [layer\_convert\_point\_to\_screen](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_convert_point_to_screen) | | Converts a point from the layer's local coordinate system to screen coordinates. | | c | [layer\_convert\_rect\_to\_screen](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_convert_rect_to_screen) | | Converts a rectangle from the layer's local coordinate system to screen coordinates. | | c | [layer\_get\_window](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_window) | | Gets the window that the layer is currently attached to. | | c | [layer\_remove\_from\_parent](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_remove_from_parent) | | Removes the layer from its current parent layer If removed successfully, the child's parent layer will be marked dirty automatically. | | c | [layer\_remove\_child\_layers](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_remove_child_layers) | | Removes child layers from given layer If removed successfully, the child's parent layer will be marked dirty automatically. | | c | [layer\_add\_child](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_add_child) | | Adds the child layer to a given parent layer, making it appear in front of its parent and in front of any existing child layers of the parent. If the child layer was already part of a layer hierarchy, it will be removed from its old parent first. If added successfully, the parent (and children) will be marked dirty automatically. | | c | [layer\_insert\_below\_sibling](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_insert_below_sibling) | | Inserts the layer as a sibling behind another layer. If the layer to insert was already part of a layer hierarchy, it will be removed from its old parent first. The below\_layer has to be a child of a parent layer, otherwise this function will be a noop. If inserted successfully, the parent (and children) will be marked dirty automatically. | | c | [layer\_insert\_above\_sibling](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_insert_above_sibling) | | Inserts the layer as a sibling in front of another layer. The above\_layer has to be a child of a parent layer, otherwise this function will be a noop. If inserted successfully, the parent (and children) will be marked dirty automatically. | | c | [layer\_set\_hidden](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_hidden) | | Sets the visibility of the layer. If the visibility has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically on the parent layer. | | c | [layer\_get\_hidden](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_hidden) | | Gets the visibility of the layer. | | c | [layer\_set\_clips](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_set_clips) | | Sets whether clipping is enabled for the layer. If enabled, whatever the layer _and its children_ will draw using their `.update_proc` callbacks, will be clipped by the this layer's frame. If the clipping has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically. | | c | [layer\_get\_clips](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_clips) | | Gets whether clipping is enabled for the layer. If enabled, whatever the layer _and its children_ will draw using their `.update_proc` callbacks, will be clipped by the this layer's frame. | | c | [layer\_get\_data](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_get_data) | | Gets the data from a layer that has been created with an extra data region. | | c | [ActionBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md) | | Vertical, bar-shaped control widget on the right edge of the window | | c | [ACTION\_BAR\_WIDTH](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ACTION_BAR_WIDTH) | | The width of the action bar in pixels. | | c | [\_ACTION\_BAR\_WIDTH](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#_ACTION_BAR_WIDTH) | | The width of the action bar in pixels, for all platforms. | | c | [NUM\_ACTION\_BAR\_ITEMS](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#NUM_ACTION_BAR_ITEMS) | | The maximum number of action bar items. | | c | [ActionBarLayerIconPressAnimation](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ActionBarLayerIconPressAnimation) | | | | c | [ActionBarLayerIconPressAnimationNone](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ActionBarLayerIconPressAnimationNone) | | | | c | [ActionBarLayerIconPressAnimationMoveLeft](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ActionBarLayerIconPressAnimationMoveLeft) | | | | c | [ActionBarLayerIconPressAnimationMoveUp](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ActionBarLayerIconPressAnimationMoveUp) | | | | c | [ActionBarLayerIconPressAnimationMoveRight](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ActionBarLayerIconPressAnimationMoveRight) | | | | c | [ActionBarLayerIconPressAnimationMoveDown](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ActionBarLayerIconPressAnimationMoveDown) | | | | c | [ActionBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#ActionBarLayer) | | | | c | [action\_bar\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_create) | | Creates a new ActionBarLayer on the heap and initalizes it with the default values. | | c | [action\_bar\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_destroy) | | Destroys a ActionBarLayer previously created by action\_bar\_layer\_create. | | c | [action\_bar\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_get_layer) | | Gets the "root" Layer of the action bar layer, which is the parent for the sub- layers used for its implementation. | | c | [action\_bar\_layer\_set\_context](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_context) | | Sets the context parameter, which will be passed in to [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) callbacks and the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) callback of the action bar. | | c | [action\_bar\_layer\_set\_click\_config\_provider](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_click_config_provider) | | Sets the click configuration provider callback of the action bar. In this callback your application can associate handlers to the different types of click events for each of the buttons, see [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md). | | c | [action\_bar\_layer\_set\_icon](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon) | | Sets an action bar icon onto one of the 3 slots as identified by `button_id`. Only BUTTON\_ID\_UP, BUTTON\_ID\_SELECT and BUTTON\_ID\_DOWN can be used. The transition will not be animated. Whenever an icon is set, the click configuration provider will be called, to give the application the opportunity to reconfigure the button interaction. | | c | [action\_bar\_layer\_clear\_icon](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_clear_icon) | | Convenience function to clear out an existing icon. All it does is call `action_bar_layer_set_icon(action_bar, button_id, NULL)` | | c | [action\_bar\_layer\_add\_to\_window](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_add_to_window) | | Adds the action bar's layer on top of the window's root layer. It also adjusts the layout of the action bar to match the geometry of the window it gets added to. Lastly, it calls [window\_set\_click\_config\_provider\_with\_context()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) on the window to set it up to work with the internal callback and raw click handlers of the action bar, to enable the highlighting of the section of the action bar when the user presses a button. | | c | [action\_bar\_layer\_remove\_from\_window](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_remove_from_window) | | Removes the action bar from the window and unconfigures the window's click configuration provider. `NULL` is set as the window's new click config provider and also as its callback context. If it has not been added to a window before, this function is a no-op. | | c | [action\_bar\_layer\_set\_background\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_background_color) | | Sets the background color of the action bar. Defaults to [GColorBlack](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlack). The action bar's layer is automatically marked dirty. | | c | [action\_bar\_layer\_set\_icon\_animated](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_animated) | | Sets an action bar icon onto one of the 3 slots as identified by `button_id`. Only BUTTON\_ID\_UP, BUTTON\_ID\_SELECT and BUTTON\_ID\_DOWN can be used. Optionally, if `animated` is `true`, the transition will be animated. Whenever an icon is set, the click configuration provider will be called, to give the application the opportunity to reconfigure the button interaction. | | c | [action\_bar\_layer\_set\_icon\_press\_animation](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md#action_bar_layer_set_icon_press_animation) | | Sets the animation to use while a button is pressed on an ActionBarLayer. By default we use ActionBarLayerIconPressAnimationMoveLeft. | | c | [BitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md) | | Layer that displays a bitmap image. | | c | [BitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#BitmapLayer) | | | | c | [bitmap\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_create) | | Creates a new bitmap layer on the heap and initalizes it the default values. | | c | [bitmap\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_destroy) | | Destroys a window previously created by bitmap\_layer\_create. | | c | [bitmap\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_get_layer) | | Gets the "root" Layer of the bitmap layer, which is the parent for the sub- layers used for its implementation. | | c | [bitmap\_layer\_get\_bitmap](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_get_bitmap) | | Gets the pointer to the bitmap image that the BitmapLayer is using. | | c | [bitmap\_layer\_set\_bitmap](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_bitmap) | | Sets the bitmap onto the BitmapLayer. The bitmap is set by reference (no deep copy), thus the caller of this function has to make sure the bitmap is kept in memory. | | c | [bitmap\_layer\_set\_alignment](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_alignment) | | Sets the alignment of the image to draw with in frame of the BitmapLayer. The aligment parameter specifies which edges of the bitmap should overlap with the frame of the BitmapLayer. If the bitmap is smaller than the frame of the BitmapLayer, the background is filled with the background color. | | c | [bitmap\_layer\_set\_background\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_background_color) | | Sets the background color of bounding box that will be drawn behind the image of the BitmapLayer. | | c | [bitmap\_layer\_set\_compositing\_mode](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md#bitmap_layer_set_compositing_mode) | | Sets the compositing mode of how the bitmap image is composited onto the BitmapLayer's background plane, or how it is composited onto what has been drawn beneath the BitmapLayer. | | c | [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) | | Layer that displays a standard list menu. Data is provided using callbacks. | | c | [menu\_cell\_basic\_draw](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_basic_draw) | | Section drawing function to draw a basic section cell with the title, subtitle, and icon of the section. Call this function inside the `.draw_row` callback implementation, see [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks). Note that if the size of `cell_layer` is too small to fit all of the cell items specified, not all of them may be drawn. | | c | [menu\_cell\_title\_draw](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_title_draw) | | Cell drawing function to draw a basic menu cell layout with title, subtitle Cell drawing function to draw a menu cell layout with only one big title. Call this function inside the `.draw_row` callback implementation, see [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks). | | c | [menu\_cell\_basic\_header\_draw](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_basic_header_draw) | | Section header drawing function to draw a basic section header cell layout with the title of the section. Call this function inside the `.draw_header` callback implementation, see [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks). | | c | [MENU\_CELL\_BASIC\_HEADER\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_BASIC_HEADER_HEIGHT) | | Default section header height in pixels. | | c | [MENU\_INDEX\_NOT\_FOUND](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_INDEX_NOT_FOUND) | | | | c | [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) | | Macro to create a [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). | | c | [menu\_index\_compare](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_index_compare) | | Comparator function to determine the order of two [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) values. | | c | [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayer) | | | | c | [MenuLayerGetNumberOfSectionsCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetNumberOfSectionsCallback) | | Function signature for the callback to get the number of sections in a menu. | | c | [MenuLayerGetNumberOfRowsInSectionsCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetNumberOfRowsInSectionsCallback) | | Function signature for the callback to get the number of rows in a given section in a menu. | | c | [MenuLayerGetCellHeightCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetCellHeightCallback) | | Function signature for the callback to get the height of the menu cell at a given index. | | c | [MenuLayerGetHeaderHeightCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetHeaderHeightCallback) | | Function signature for the callback to get the height of the section header at a given section index. | | c | [MenuLayerGetSeparatorHeightCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerGetSeparatorHeightCallback) | | Function signature for the callback to get the height of the separator at a given index. | | c | [MenuLayerDrawRowCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawRowCallback) | | Function signature for the callback to render the menu cell at a given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). | | c | [MenuLayerDrawHeaderCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawHeaderCallback) | | Function signature for the callback to render the section header at a given section index. | | c | [MenuLayerDrawSeparatorCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawSeparatorCallback) | | Function signature for the callback to render the separator at a given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). | | c | [MenuLayerSelectCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerSelectCallback) | | Function signature for the callback to handle the event that a user hits the SELECT button. | | c | [MenuLayerSelectionChangedCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerSelectionChangedCallback) | | Function signature for the callback to handle a change in the current selected item in the menu. | | c | [MenuLayerSelectionWillChangeCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerSelectionWillChangeCallback) | | Function signature for the callback which allows or changes selection behavior of the menu. In order to change the cell that should be selected, modify the passed in new\_index. Preventing the selection from changing, new\_index can be assigned the value of old\_index. | | c | [MenuLayerDrawBackgroundCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawBackgroundCallback) | | Function signature for the callback which draws the menu's background. The background is underneath the cells of the menu, and is visible in the padding below the bottom cell, or if a cell's background color is set to GColorClear. | | c | [menu\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_create) | | Creates a new [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) on the heap and initalizes it with the default values. | | c | [menu\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_destroy) | | Destroys a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) previously created by menu\_layer\_create. | | c | [menu\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_get_layer) | | Gets the "root" Layer of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), which is the parent for the sub- layers used for its implementation. | | c | [menu\_layer\_get\_scroll\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_get_scroll_layer) | | Gets the ScrollLayer of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), which is the layer responsible for the scrolling of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). | | c | [menu\_layer\_set\_callbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_callbacks) | | Sets the callbacks for the MenuLayer. | | c | [menu\_layer\_set\_click\_config\_onto\_window](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_click_config_onto_window) | | Convenience function to set the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) callback on the given window to the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) internal click config provider. This internal click configuration provider, will set up the default UP & DOWN scrolling / menu item selection behavior. This function calls [scroll\_layer\_set\_click\_config\_onto\_window](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window) to accomplish this. | | c | [MenuRowAlign](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuRowAlign) | | Values to specify how a (selected) row should be aligned relative to the visible area of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). | | c | [MenuRowAlignNone](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuRowAlignNone) | | Don't align or update the scroll offset of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). | | c | [MenuRowAlignCenter](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuRowAlignCenter) | | Scroll the contents of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) in such way that the selected row is centered relative to the visible area. | | c | [MenuRowAlignTop](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuRowAlignTop) | | Scroll the contents of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) in such way that the selected row is at the top of the visible area. | | c | [MenuRowAlignBottom](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuRowAlignBottom) | | Scroll the contents of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) in such way that the selected row is at the bottom of the visible area. | | c | [menu\_layer\_set\_selected\_next](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_selected_next) | | Selects the next or previous item, relative to the current selection. | | c | [menu\_layer\_set\_selected\_index](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_selected_index) | | Selects the item with given [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex). | | c | [menu\_layer\_get\_selected\_index](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_get_selected_index) | | Gets the [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) of the currently selected menu item. | | c | [menu\_layer\_reload\_data](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_reload_data) | | Reloads the data of the menu. This causes the menu to re-request the menu item data, by calling the relevant callbacks. The current selection and scroll position will not be changed. See the note with [menu\_layer\_set\_selected\_index()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_selected_index) for the behavior if the old selection is no longer valid. | | c | [menu\_cell\_layer\_is\_highlighted](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_cell_layer_is_highlighted) | | Returns whether or not the given cell layer is highlighted. Using this for determining highlight behaviour is preferable to using [menu\_layer\_get\_selected\_index](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_get_selected_index). Row drawing callbacks may be invoked multiple times with a different highlight status on the same cell in order to handle partially highlighted cells during animation. | | c | [menu\_layer\_set\_normal\_colors](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_normal_colors) | | Set the default colors to be used for cells when it is in a normal state (not highlighted). The GContext's text and fill colors will be set appropriately prior to calling the `.draw_row` callback. If this function is not explicitly called on a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), it will default to white background with black foreground. | | c | [menu\_layer\_set\_highlight\_colors](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_highlight_colors) | | Set the default colors to be used for cells when it is in a highlighted state. The GContext's text and fill colors will be set appropriately prior to calling the `.draw_row` callback. If this function is not explicitly called on a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), it will default to black background with white foreground. | | c | [menu\_layer\_pad\_bottom\_enable](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_pad_bottom_enable) | | This enables or disables padding at the bottom of the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). Padding at the bottom of the layer keeps the bottom item from being at the very bottom of the screen. Padding is turned on by default for all MenuLayers. The color of the padded area will be the background color set using [menu\_layer\_set\_normal\_colors()](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_normal_colors). To color the padding a different color, use [MenuLayerDrawBackgroundCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerDrawBackgroundCallback). | | c | [menu\_layer\_get\_center\_focused](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_get_center_focused) | | True, if the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) generally scrolls such that the selected row is in the center. | | c | [menu\_layer\_set\_center\_focused](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_set_center_focused) | | Controls if the [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) generally scrolls such that the selected row is in the center. For platforms with a round display (PBL\_ROUND) the default is true, otherwise false is the default. | | c | [menu\_layer\_is\_index\_selected](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#menu_layer_is_index_selected) | | Returns whether or not the specified cell index is currently selected. | | c | [MENU\_CELL\_ROUND\_FOCUSED\_SHORT\_CELL\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT) | | Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) short cell height when this item is the selected item on a round display. | | c | [MENU\_CELL\_ROUND\_UNFOCUSED\_SHORT\_CELL\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT) | | Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) short cell height when this item is not the selected item on a round display. | | c | [MENU\_CELL\_ROUND\_FOCUSED\_TALL\_CELL\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT) | | Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) tall cell height when this item is the selected item on a round display. | | c | [MENU\_CELL\_ROUND\_UNFOCUSED\_TALL\_CELL\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT) | | Constant value representing [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) tall cell height when this item is not the selected item on a round display. | | c | [MenuCellSpan](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuCellSpan) | | | | c | [MenuIndex](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuIndex) | | Data structure to represent an menu item's position in a menu, by specifying the section index and the row index within that section. | | c | [MenuLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md#MenuLayerCallbacks) | | Data structure containing all the callbacks of a [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md). | | c | [RotBitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md) | | Layer that displays a rotated bitmap image. | | c | [RotBitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#RotBitmapLayer) | | | | c | [rot\_bitmap\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_create) | | Creates a new RotBitmapLayer on the heap and initializes it with the default values: | | c | [rot\_bitmap\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_destroy) | | Destroys a RotBitmapLayer and frees all associated memory. | | c | [rot\_bitmap\_layer\_set\_corner\_clip\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_set_corner_clip_color) | | Defines what color to use in areas that are not covered by the source bitmap. By default this is GColorClear. | | c | [rot\_bitmap\_layer\_set\_angle](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_set_angle) | | Sets the rotation angle of this RotBitmapLayer. | | c | [rot\_bitmap\_layer\_increment\_angle](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_increment_angle) | | Change the rotation angle of this RotBitmapLayer. | | c | [rot\_bitmap\_set\_src\_ic](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_set_src_ic) | | Defines the only point that will not be affected by the rotation in the source bitmap. | | c | [rot\_bitmap\_set\_compositing\_mode](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_set_compositing_mode) | | Sets the compositing mode of how the bitmap image is composited onto what has been drawn beneath the RotBitmapLayer. By default this is GCompOpAssign, i.e. transparency disabled. The RotBitmapLayer is automatically marked dirty after this operation. | | c | [rot\_bitmap\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md#rot_bitmap_layer_get_layer) | | Gets the "root" Layer of the RotBitmapLayer, which is the parent for the sub- layers used for its implementation. | | c | [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md) | | Layer that scrolls its contents, animated. | | c | [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ScrollLayer) | | | | c | [ScrollLayerCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ScrollLayerCallback) | | Function signature for the `.content_offset_changed_handler` callback. | | c | [scroll\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_create) | | Creates a new ScrollLayer on the heap and initalizes it with the default values: | | c | [scroll\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_destroy) | | Destroys a ScrollLayer previously created by scroll\_layer\_create. | | c | [scroll\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_layer) | | Gets the "root" Layer of the scroll layer, which is the parent for the sub- layers used for its implementation. | | c | [scroll\_layer\_add\_child](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_add_child) | | Adds the child layer to the content sub-layer of the ScrollLayer. This will make the child layer part of the scrollable contents. The content sub-layer of the ScrollLayer will become the parent of the child layer. | | c | [scroll\_layer\_set\_click\_config\_onto\_window](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window) | | Convenience function to set the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) callback on the given window to scroll layer's internal click config provider. This internal click configuration provider, will set up the default UP & DOWN scrolling behavior. This function calls [window\_set\_click\_config\_provider\_with\_context](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) to accomplish this. | | c | [scroll\_layer\_set\_callbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_callbacks) | | Sets the callbacks that the scroll layer exposes. The `context` as set by [scroll\_layer\_set\_context()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_context) is passed into each of the callbacks. See [ScrollLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ScrollLayerCallbacks) for the different callbacks. | | c | [scroll\_layer\_set\_context](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_context) | | Sets a new callback context. This context is passed into the scroll layer's callbacks and also the [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) for the SELECT button. If `NULL` or not set, the context defaults to a pointer to the ScrollLayer itself. | | c | [scroll\_layer\_set\_content\_offset](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_offset) | | Scrolls to the given offset, optionally animated. | | c | [scroll\_layer\_get\_content\_offset](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_content_offset) | | Gets the point by which the contents are offset. | | c | [scroll\_layer\_set\_content\_size](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_content_size) | | Sets the size of the contents layer. This determines the area that is scrollable. At the moment, this needs to be set "manually" and is not derived from the geometry of the contents layers. | | c | [scroll\_layer\_get\_content\_size](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_content_size) | | Gets the size of the contents layer. | | c | [scroll\_layer\_set\_frame](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_frame) | | Set the frame of the scroll layer and adjusts the internal layers' geometry accordingly. The scroll layer is marked dirty automatically. | | c | [scroll\_layer\_scroll\_up\_click\_handler](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_scroll_up_click_handler) | | The click handlers for the UP button that the scroll layer will install as part of [scroll\_layer\_set\_click\_config\_onto\_window()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window). | | c | [scroll\_layer\_scroll\_down\_click\_handler](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_scroll_down_click_handler) | | The click handlers for the DOWN button that the scroll layer will install as part of [scroll\_layer\_set\_click\_config\_onto\_window()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_click_config_onto_window). | | c | [scroll\_layer\_set\_shadow\_hidden](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_shadow_hidden) | | Sets the visibility of the scroll layer shadow. If the visibility has changed, [layer\_mark\_dirty()](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_mark_dirty) will be called automatically on the scroll layer. | | c | [scroll\_layer\_get\_shadow\_hidden](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_shadow_hidden) | | Gets the visibility of the scroll layer shadow. | | c | [scroll\_layer\_set\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_set_paging) | | Enables or disables paging of the ScrollLayer (default: disabled). When enabled, every button press will change the scroll offset by the frame's height. | | c | [scroll\_layer\_get\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_paging) | | Check whether or not the ScrollLayer uses paging when pressing buttons. | | c | [ContentIndicator](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicator) | | | | c | [scroll\_layer\_get\_content\_indicator](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#scroll_layer_get_content_indicator) | | Gets the ContentIndicator for a ScrollLayer. | | c | [ContentIndicatorDirection](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorDirection) | | Value to describe directions for ContentIndicator. | | c | [ContentIndicatorDirectionUp](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorDirectionUp) | | The up direction. | | c | [ContentIndicatorDirectionDown](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorDirectionDown) | | The down direction. | | c | [NumContentIndicatorDirections](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#NumContentIndicatorDirections) | | The number of supported directions. | | c | [content\_indicator\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_create) | | Creates a ContentIndicator on the heap. | | c | [content\_indicator\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_destroy) | | Destroys a ContentIndicator previously created using [content\_indicator\_create()](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_create). | | c | [content\_indicator\_configure\_direction](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_configure_direction) | | Configures a ContentIndicator for the given direction. | | c | [content\_indicator\_get\_content\_available](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_get_content_available) | | Retrieves the availability status of content in the given direction. | | c | [content\_indicator\_set\_content\_available](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#content_indicator_set_content_available) | | Sets the availability status of content in the given direction. | | c | [ContentIndicatorConfig](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ContentIndicatorConfig) | | Struct used to configure directions for ContentIndicator. | | c | [ScrollLayerCallbacks](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md#ScrollLayerCallbacks) | | All the callbacks that the ScrollLayer exposes for use by applications. | | c | [SimpleMenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md) | | Wrapper around [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md), that uses static data to display a list menu. | | c | [SimpleMenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#SimpleMenuLayer) | | | | c | [SimpleMenuLayerSelectCallback](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#SimpleMenuLayerSelectCallback) | | Function signature for the callback to handle the event that a user hits the SELECT button. | | c | [simple\_menu\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#simple_menu_layer_create) | | Creates a new SimpleMenuLayer on the heap and initializes it. It also sets the internal click configuration provider onto given window. | | c | [simple\_menu\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#simple_menu_layer_destroy) | | Destroys a SimpleMenuLayer previously created by simple\_menu\_layer\_create. | | c | [simple\_menu\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#simple_menu_layer_get_layer) | | Gets the "root" Layer of the simple menu layer, which is the parent for the sub-layers used for its implementation. | | c | [simple\_menu\_layer\_get\_selected\_index](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#simple_menu_layer_get_selected_index) | | Gets the row index of the currently selection menu item. | | c | [simple\_menu\_layer\_set\_selected\_index](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#simple_menu_layer_set_selected_index) | | Selects the item in the first section at given row index. | | c | [simple\_menu\_layer\_get\_menu\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#simple_menu_layer_get_menu_layer) | | | | c | [SimpleMenuItem](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#SimpleMenuItem) | | Data structure containing the information of a menu item. | | c | [SimpleMenuSection](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md#SimpleMenuSection) | | Data structure containing the information of a menu section. | | c | [StatusBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md) | | Layer that serves as a configurable status bar. | | c | [StatusBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#StatusBarLayer) | | | | c | [StatusBarLayerSeparatorMode](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#StatusBarLayerSeparatorMode) | | Values that are used to indicate the different status bar separator modes. | | c | [StatusBarLayerSeparatorModeNone](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#StatusBarLayerSeparatorModeNone) | | The default mode. No separator will be shown. | | c | [StatusBarLayerSeparatorModeDotted](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#StatusBarLayerSeparatorModeDotted) | | A dotted separator at the bottom of the status bar. | | c | [STATUS\_BAR\_LAYER\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#STATUS_BAR_LAYER_HEIGHT) | | The fixed height of the status bar, including separator height. | | c | [\_STATUS\_BAR\_LAYER\_HEIGHT](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#_STATUS_BAR_LAYER_HEIGHT) | | The fixed height of the status bar, including separator height, for all platforms. | | c | [status\_bar\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_create) | | Creates a new StatusBarLayer on the heap and initializes it with the default values. | | c | [status\_bar\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_destroy) | | Destroys a StatusBarLayer previously created by status\_bar\_layer\_create. | | c | [status\_bar\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_get_layer) | | Gets the "root" Layer of the status bar, which is the parent for the sub- layers used for its implementation. | | c | [status\_bar\_layer\_get\_background\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_get_background_color) | | Gets background color of StatusBarLayer. | | c | [status\_bar\_layer\_get\_foreground\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_get_foreground_color) | | Gets foreground color of StatusBarLayer. | | c | [status\_bar\_layer\_set\_colors](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_set_colors) | | Sets the background and foreground colors of StatusBarLayer. | | c | [status\_bar\_layer\_set\_separator\_mode](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md#status_bar_layer_set_separator_mode) | | Sets the mode of the StatusBarLayer separator, to help divide it from content. | | c | [TextLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md) | | Layer that displays and formats a text string. | | c | [TextLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#TextLayer) | | | | c | [text\_layer\_create](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_create) | | Creates a new TextLayer on the heap and initializes it with the default values. | | c | [text\_layer\_destroy](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_destroy) | | Destroys a TextLayer previously created by text\_layer\_create. | | c | [text\_layer\_get\_layer](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_get_layer) | | Gets the "root" Layer of the text layer, which is the parent for the sub- layers used for its implementation. | | c | [text\_layer\_set\_text](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text) | | Sets the pointer to the string where the TextLayer is supposed to find the text at a later point in time, when it needs to draw itself. | | c | [text\_layer\_get\_text](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_get_text) | | Gets the pointer to the string that the TextLayer is using. | | c | [text\_layer\_set\_background\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_background_color) | | Sets the background color of the bounding box that will be drawn behind the text. | | c | [text\_layer\_set\_text\_color](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text_color) | | Sets the color of text that will be drawn. | | c | [text\_layer\_set\_overflow\_mode](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_overflow_mode) | | Sets the line break mode of the TextLayer. | | c | [text\_layer\_set\_font](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_font) | | Sets the font of the TextLayer. | | c | [text\_layer\_set\_text\_alignment](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text_alignment) | | Sets the alignment of the TextLayer. | | c | [text\_layer\_enable\_screen\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_enable_screen_text_flow_and_paging) | | Enables text flow following the boundaries of the screen and pagination that introduces extra line spacing at page breaks to avoid partially clipped lines for the TextLayer. If the TextLayer is part of a [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md) the ScrollLayer's frame will be used to configure paging. | | c | [text\_layer\_restore\_default\_text\_flow\_and\_paging](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_restore_default_text_flow_and_paging) | | Restores text flow and paging for the TextLayer to the rectangular defaults. | | c | [text\_layer\_get\_content\_size](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_get_content_size) | | Calculates the size occupied by the current text of the TextLayer. | | c | [text\_layer\_set\_size](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_size) | | Update the size of the text layer This is a convenience function to update the frame of the TextLayer. | | c | [Light](https://developer.repebble.com/docs/c/User_Interface/Light.md) | | Controlling Pebble's backlight | | c | [light\_enable\_interaction](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_enable_interaction) | | Trigger the backlight and schedule a timer to automatically disable the backlight after a short delay. This is the preferred method of interacting with the backlight. | | c | [light\_enable](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_enable) | | Turn the watch's backlight on or put it back into automatic control. Developers should take care when calling this function, keeping Pebble's backlight on for long periods of time will rapidly deplete the battery. | | c | [light\_is\_on](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_is_on) | | | | c | [light\_set\_color](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_color) | | Tint the backlight LED to the given color. The color persists while the app is foregrounded and is automatically reset to the user's default (white) when the app exits or is preempted by a system notification. On platforms without a color backlight this is a no-op. | | c | [light\_set\_color\_rgb888](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_color_rgb888) | | Tint the backlight LED to a packed 24-bit RGB value (0x00RRGGBB). Same persistence semantics as [light\_set\_color()](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_color): the override lasts while the app is foregrounded and is reset on app exit or system preempt. No-op on platforms without a color backlight. | | c | [light\_set\_system\_color](https://developer.repebble.com/docs/c/User_Interface/Light.md#light_set_system_color) | | Restore the backlight to the user's default color. Rarely needed — the system resets automatically on app exit. No-op on platforms without a color backlight. | | c | [Preferences](https://developer.repebble.com/docs/c/User_Interface/Preferences.md) | | | | c | [preferred\_result\_display\_duration](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_result_display_duration) | | Get the recommended amount of milliseconds a result window should be visible before it should automatically close. | | c | [PreferredContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSize) | | [PreferredContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSize) represents the display scale of all the app's UI components. The enum contains all sizes that all platforms as a whole are capable of displaying, but each individual platform may not be able to display all sizes. | | c | [PreferredContentSizeSmall](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSizeSmall) | | | | c | [PreferredContentSizeMedium](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSizeMedium) | | | | c | [PreferredContentSizeLarge](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSizeLarge) | | | | c | [PreferredContentSizeExtraLarge](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#PreferredContentSizeExtraLarge) | | | | c | [NumPreferredContentSizes](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#NumPreferredContentSizes) | | | | c | [preferred\_content\_size](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) | | Returns the user's preferred content size representing the scale of all the app's UI components should use for display. | | c | [quiet\_time\_is\_active](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#quiet_time_is_active) | | Users can toggle Quiet Time manually or on schedule. Watchfaces and apps should respect this choice and avoid disturbing actions such as vibration if quiet time is active. | | c | [Speaker](https://developer.repebble.com/docs/c/User_Interface/Speaker.md) | | Controlling the speaker | | c | [SpeakerWaveform](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveform) | | | | c | [SpeakerWaveformSine](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformSine) | | | | c | [SpeakerWaveformSquare](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformSquare) | | | | c | [SpeakerWaveformTriangle](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformTriangle) | | | | c | [SpeakerWaveformSawtooth](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformSawtooth) | | | | c | [SpeakerWaveformCount](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerWaveformCount) | | | | c | [SpeakerPcmFormat](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat) | | PCM audio format for speaker streaming. Bit layout: bit0 = sample rate (0=8kHz, 1=16kHz), bit1 = bit depth (0=8-bit, 1=16-bit). All formats are mono signed PCM (8-bit samples are signed [-128,127], not unsigned). | | c | [SpeakerPcmFormat\_8kHz\_8bit](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_8kHz_8bit) | | 8kHz 8-bit signed (1 byte/sample) | | c | [SpeakerPcmFormat\_16kHz\_8bit](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_16kHz_8bit) | | 16kHz 8-bit signed (1 byte/sample) | | c | [SpeakerPcmFormat\_8kHz\_16bit](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_8kHz_16bit) | | 8kHz 16-bit signed little-endian (2 bytes/sample) | | c | [SpeakerPcmFormat\_16kHz\_16bit](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat_16kHz_16bit) | | 16kHz 16-bit signed little-endian (2 bytes/sample) | | c | [SpeakerPcmFormatCount](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormatCount) | | | | c | [SpeakerStatus](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatus) | | Speaker status. | | c | [SpeakerStatusIdle](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatusIdle) | | | | c | [SpeakerStatusPlaying](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatusPlaying) | | | | c | [SpeakerStatusDraining](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerStatusDraining) | | | | c | [SpeakerFinishReason](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishReason) | | Reason reported when speaker playback ends. | | c | [SpeakerFinishReasonDone](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishReasonDone) | | Playback completed naturally. | | c | [SpeakerFinishReasonStopped](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishReasonStopped) | | Playback was stopped by the app. | | c | [SpeakerFinishReasonPreempted](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishReasonPreempted) | | Preempted by higher priority source. | | c | [SpeakerFinishReasonError](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishReasonError) | | An error occurred. | | c | [SpeakerFinishedCallback](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerFinishedCallback) | | Callback invoked when playback finishes. | | c | [SPEAKER\_MAX\_NOTES](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SPEAKER_MAX_NOTES) | | Maximum number of notes in a single [speaker\_play\_notes()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_notes) call. | | c | [SPEAKER\_MAX\_TRACKS](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SPEAKER_MAX_TRACKS) | | Maximum number of parallel tracks in a single [speaker\_play\_tracks()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tracks) call. | | c | [SPEAKER\_MAX\_SAMPLE\_BYTES\_TOTAL](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SPEAKER_MAX_SAMPLE_BYTES_TOTAL) | | Maximum total sample-data bytes across all tracks in one [speaker\_play\_tracks()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tracks) call. | | c | [speaker\_play\_notes](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_notes) | | Play a sequence of notes on the speaker. | | c | [speaker\_play\_tracks](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tracks) | | Play N monophonic tracks in parallel, mixed (polyphony). | | c | [speaker\_play\_tone](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tone) | | Play a single tone on the speaker (convenience wrapper). | | c | [speaker\_stream\_open](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_stream_open) | | Open a raw PCM stream for app-generated audio. | | c | [speaker\_stream\_write](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_stream_write) | | Write PCM data to the open stream. | | c | [speaker\_stream\_close](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_stream_close) | | Close the PCM stream. Buffered data will be played before stopping. | | c | [speaker\_stop](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_stop) | | Stop any active speaker playback immediately. | | c | [speaker\_set\_volume](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_set_volume) | | Set the speaker volume. | | c | [speaker\_get\_status](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_get_status) | | Get the current speaker status. | | c | [speaker\_set\_finish\_callback](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_set_finish_callback) | | Register a callback invoked when speaker playback ends. The callback runs on the app task. | | c | [speaker\_is\_muted](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_is_muted) | | Check whether the speaker is currently muted system-wide. The watch can mute the speaker either always-on (Settings → Sounds & Haptics) or for the duration of Quiet Time (Settings → Quiet Time). Apps cannot override the mute, but they can use this to adapt UI or skip long sounds the user won't hear. | | c | [SpeakerNote](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerNote) | | A single note in a sequence. midi\_note: MIDI note number (0-127, 60=C4). 0 = rest (silence). waveform: SpeakerWaveform value. duration\_ms: Note duration in ms (max 10000). velocity: Volume 0-127 (0 = use global volume). | | c | [SpeakerSample](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerSample) | | A raw PCM sample that can be pitch-shifted when played by a track. data: mono signed PCM in the given format. num\_bytes: size of data in bytes. format: sample rate + bit depth (see [SpeakerPcmFormat](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerPcmFormat)). base\_midi\_note: the MIDI note at which the sample plays unshifted (e.g. 60 = C4). Notes above/below this value are produced by resampling. loop: if true, the sample restarts from the beginning each time it runs out, and keeps playing until the owning note's duration elapses. | | c | [SpeakerTrack](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#SpeakerTrack) | | A single monophonic voice. Multiple tracks are mixed together by [speaker\_play\_tracks()](https://developer.repebble.com/docs/c/User_Interface/Speaker.md#speaker_play_tracks) to produce polyphony. notes: array of notes to play sequentially. num\_notes: length of the notes array. sample: if non-NULL, notes are played by pitch-shifting this sample; note.waveform is ignored. If NULL, notes use their waveform field. | | c | [UnobstructedArea](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md) | | | | c | [UnobstructedAreaWillChangeHandler](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaWillChangeHandler) | | Handler that will be called just before the unobstructed area will begin changing. | | c | [UnobstructedAreaChangeHandler](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaChangeHandler) | | Handler that will be called every time the unobstructed area changes. | | c | [UnobstructedAreaDidChangeHandler](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaDidChangeHandler) | | Handler that will be called after the unobstructed area has finished changing. | | c | [unobstructed\_area\_service\_subscribe](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_subscribe) | | Subscribe to be notified when the app's unobstructed area changes. When an unobstructed area begins changing, the `will_change` handler will be called, and every `will_change` call is always paired with a `did_change` call that occurs when it is done changing given that the `will_change` and `did_change` handlers are set. When subscribing while the unobstructed area is changing, the `will_change` handler will be called after subscription in the next event loop. | | c | [unobstructed\_area\_service\_unsubscribe](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#unobstructed_area_service_unsubscribe) | | Unsubscribe from notifications about changes to the app's unobstructed area. | | c | [UnobstructedAreaHandlers](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md#UnobstructedAreaHandlers) | | | | c | [Vibes](https://developer.repebble.com/docs/c/User_Interface/Vibes.md) | | Controlling the vibration motor | | c | [vibes\_cancel](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_cancel) | | Cancel any in-flight vibe patterns; this is a no-op if there is no on-going vibe. | | c | [vibes\_short\_pulse](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_short_pulse) | | Makes the watch emit one short vibration. | | c | [vibes\_long\_pulse](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_long_pulse) | | Makes the watch emit one long vibration. | | c | [vibes\_double\_pulse](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_double_pulse) | | Makes the watch emit two brief vibrations. | | c | [vibes\_enqueue\_custom\_pattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#vibes_enqueue_custom_pattern) | | Makes the watch emit a 'custom' vibration pattern. | | c | [VibePattern](https://developer.repebble.com/docs/c/User_Interface/Vibes.md#VibePattern) | | Data structure describing a vibration pattern. | | c | [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md) | | The basic building block of the user interface | | c | [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md#Window) | | | | c | [WindowHandler](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler) | | Function signature for a handler that deals with transition events of a window. | | c | [window\_create](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_create) | | Creates a new Window on the heap and initalizes it with the default values. | | c | [window\_destroy](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_destroy) | | Destroys a Window previously created by window\_create. | | c | [window\_set\_click\_config\_provider](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider) | | Sets the click configuration provider callback function on the window. This will automatically setup the input handlers of the window as well to use the click recognizer subsystem. | | c | [window\_set\_click\_config\_provider\_with\_context](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider_with_context) | | Same as [window\_set\_click\_config\_provider()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_config_provider), but will assign a custom context pointer (instead of the window pointer) that will be passed into the [ClickHandler](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickHandler) click event handlers. | | c | [window\_get\_click\_config\_provider](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_click_config_provider) | | Gets the current click configuration provider of the window. | | c | [window\_get\_click\_config\_context](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_click_config_context) | | Gets the current click configuration provider context of the window. | | c | [window\_set\_window\_handlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_window_handlers) | | Sets the window handlers of the window. These handlers get called e.g. when the user enters or leaves the window. | | c | [window\_get\_root\_layer](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_root_layer) | | Gets the root Layer of the window. The root layer is the layer at the bottom of the layer hierarchy for this window. It is the window's "canvas" if you will. By default, the root layer only draws a solid fill with the window's background color. | | c | [window\_set\_background\_color](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_background_color) | | Sets the background color of the window, which is drawn automatically by the root layer of the window. | | c | [window\_is\_loaded](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_is_loaded) | | Gets whether the window has been loaded. If a window is loaded, its `.load` handler has been called (and the `.unload` handler has not been called since). | | c | [window\_set\_user\_data](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_user_data) | | Sets a pointer to developer-supplied data that the window uses, to provide a means to access the data at later times in one of the window event handlers. | | c | [window\_get\_user\_data](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_get_user_data) | | Gets the pointer to developer-supplied data that was previously set using [window\_set\_user\_data()](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_user_data). | | c | [window\_single\_click\_subscribe](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_click_subscribe) | | Subscribe to single click events. | | c | [window\_single\_repeating\_click\_subscribe](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_single_repeating_click_subscribe) | | Subscribe to single click event, with a repeat interval. A single click is detected every time "repeat\_interval\_ms" has been reached. | | c | [window\_multi\_click\_subscribe](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_multi_click_subscribe) | | Subscribe to multi click events. | | c | [window\_long\_click\_subscribe](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_long_click_subscribe) | | Subscribe to long click events. | | c | [window\_raw\_click\_subscribe](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_raw_click_subscribe) | | Subscribe to raw click events. | | c | [window\_set\_click\_context](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_click_context) | | Set the context that will be passed to handlers for the given button's events. By default the context passed to handlers is equal to the [ClickConfigProvider](https://developer.repebble.com/docs/c/User_Interface/Clicks.md#ClickConfigProvider) context (defaults to the window). | | c | [window\_attach\_recognizer](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_attach_recognizer) | | Attach a recognizer to the window. | | c | [window\_detach\_recognizer](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_detach_recognizer) | | Detach a recognizer from the window. | | c | [window\_set\_touch\_bridge\_disabled](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_set_touch_bridge_disabled) | | Disable the system touch-navigation bridge for a window. With the bridge disabled the system recognizer set fails on Touchdown for this window, handing every touch to the recognizers the app attached, so an app can take over touch input instead of the built-in button emulation. | | c | [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) | | [WindowHandlers](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandlers) These handlers are called by the [Window Stack](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md) as windows get pushed on / popped. All these handlers use [WindowHandler](https://developer.repebble.com/docs/c/User_Interface/Window.md#WindowHandler) as their function signature. | | c | [ActionMenu](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md) | | | | c | [ActionMenuItem](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuItem) | | | | c | [ActionMenuLevel](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuLevel) | | | | c | [ActionMenuAlign](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuAlign) | | | | c | [ActionMenuAlignTop](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuAlignTop) | | | | c | [ActionMenuAlignCenter](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuAlignCenter) | | | | c | [ActionMenu](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenu) | | | | c | [ActionMenuDidCloseCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuDidCloseCb) | | Callback executed after the ActionMenu has closed, so memory may be freed. | | c | [ActionMenuLevelDisplayMode](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuLevelDisplayMode) | | enum value that controls whether menu items are displayed in a grid (similarly to the emoji replies) or in a single column (reminiscent of [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md)) | | c | [ActionMenuLevelDisplayModeWide](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuLevelDisplayModeWide) | | Each item gets its own row. | | c | [ActionMenuLevelDisplayModeThin](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuLevelDisplayModeThin) | | Grid view: multiple items per row. | | c | [ActionMenuPerformActionCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuPerformActionCb) | | Callback executed when a given action is selected. | | c | [ActionMenuEachItemCb](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuEachItemCb) | | Callback invoked for each item in an action menu hierarchy. | | c | [action\_menu\_item\_get\_label](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_item_get_label) | | Getter for the label of a given ActionMenuItem. | | c | [action\_menu\_item\_get\_action\_data](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_item_get_action_data) | | Getter for the action\_data pointer of a given ActionMenuItem. | | c | [action\_menu\_level\_create](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_create) | | Create a new action menu level with storage allocated for a given number of items. | | c | [action\_menu\_level\_set\_display\_mode](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_set_display_mode) | | Set the action menu display mode. | | c | [action\_menu\_level\_add\_action](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_add_action) | | Add an action to an ActionLevel. | | c | [action\_menu\_level\_add\_child](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_level_add_child) | | Add a child to this ActionMenuLevel. | | c | [action\_menu\_hierarchy\_destroy](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_hierarchy_destroy) | | Destroy a hierarchy of ActionMenuLevels. | | c | [action\_menu\_get\_context](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_get_context) | | Get the context pointer this ActionMenu was created with. | | c | [action\_menu\_get\_root\_level](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_get_root_level) | | Get the root level of an ActionMenu. | | c | [action\_menu\_open](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_open) | | Open a new ActionMenu. The ActionMenu acts much like a window. It fills the whole screen and handles clicks. | | c | [action\_menu\_freeze](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_freeze) | | Freeze the ActionMenu. The ActionMenu will no longer respond to user input. | | c | [action\_menu\_unfreeze](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_unfreeze) | | Unfreeze the ActionMenu previously frozen with [action\_menu\_freeze](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_freeze). | | c | [action\_menu\_set\_result\_window](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_set_result_window) | | Set the result window for an ActionMenu. The result window will be shown when the ActionMenu closes. | | c | [action\_menu\_close](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#action_menu_close) | | Close the ActionMenu, whether it is frozen or not. | | c | [ActionMenuConfig](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md#ActionMenuConfig) | | Configuration struct for the ActionMenu. | | c | [NumberWindow](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md) | | A ready-made Window prompting the user to pick a number | | c | [NumberWindow](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#NumberWindow) | | | | c | [NumberWindowCallback](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#NumberWindowCallback) | | Function signature for NumberWindow callbacks. | | c | [number\_window\_create](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_create) | | Creates a new NumberWindow on the heap and initalizes it with the default values. | | c | [number\_window\_destroy](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_destroy) | | Destroys a NumberWindow previously created by number\_window\_create. | | c | [number\_window\_set\_label](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_set_label) | | Sets the text of the title or prompt label. | | c | [number\_window\_set\_max](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_set_max) | | Sets the maximum value this field can hold. | | c | [number\_window\_set\_min](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_set_min) | | Sets the minimum value this field can hold. | | c | [number\_window\_set\_value](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_set_value) | | Sets the current value of the field. | | c | [number\_window\_set\_step\_size](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_set_step_size) | | Sets the amount by which to increment/decrement by on a button click. | | c | [number\_window\_get\_value](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_get_value) | | Gets the current value. | | c | [number\_window\_get\_window](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#number_window_get_window) | | Gets the "root" Window of the number window. | | c | [NumberWindowCallbacks](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md#NumberWindowCallbacks) | | Data structure containing all the callbacks for a NumberWindow. | | c | [Window Stack](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md) | | The multiple window manager | | c | [window\_stack\_push](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_push) | | Pushes the given window on the window navigation stack, on top of the current topmost window of the app. | | c | [window\_stack\_pop](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_pop) | | Pops the topmost window on the navigation stack. | | c | [window\_stack\_pop\_all](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_pop_all) | | Pops all windows. See [window\_stack\_remove()](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_remove) for a description of the `animated` parameter and notes. | | c | [window\_stack\_remove](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_remove) | | Removes a given window from the window stack that belongs to the app task. | | c | [window\_stack\_get\_top\_window](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_get_top_window) | | Gets the topmost window on the stack that belongs to the app. | | c | [window\_stack\_contains\_window](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_contains_window) | | Checks if the window is on the window stack. | | c | [Worker](https://developer.repebble.com/docs/c/Worker.md) | | | | c | [worker\_event\_loop](https://developer.repebble.com/docs/c/Worker.md#worker_event_loop) | | The event loop for workers, to be used in worker's main(). Will block until the worker is ready to exit. | | c | [worker\_launch\_app](https://developer.repebble.com/docs/c/Worker.md#worker_launch_app) | | Launch the foreground app for this worker. | | c | [Standard C](https://developer.repebble.com/docs/c/Standard_C.md) | | | | c | [uint16\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint16_t) | | 16-bit unsigned integer number | | c | [uint32\_t](https://developer.repebble.com/docs/c/Standard_C.md#uint32_t) | | 32-bit unsigned integer number | | c | [Format](https://developer.repebble.com/docs/c/Standard_C/Format.md) | | Standard formatting. | | c | [snprintf](https://developer.repebble.com/docs/c/Standard_C/Format.md#snprintf) | | Format a string into a buffer. | | c | [Locale](https://developer.repebble.com/docs/c/Standard_C/Locale.md) | | Standard locale functions. | | c | [setlocale](https://developer.repebble.com/docs/c/Standard_C/Locale.md#setlocale) | | Set the app's locale for a category of routines. | | c | [Math](https://developer.repebble.com/docs/c/Standard_C/Math.md) | | Standard math functions. | | c | [RAND\_MAX](https://developer.repebble.com/docs/c/Standard_C/Math.md#RAND_MAX) | | The maximum integer value [rand()](https://developer.repebble.com/docs/c/Standard_C/Math.md#rand) may return. | | c | [rand](https://developer.repebble.com/docs/c/Standard_C/Math.md#rand) | | Generate a pseudo-random integer between 0 and [RAND\_MAX](https://developer.repebble.com/docs/c/Standard_C/Math.md#RAND_MAX) inclusive. | | c | [srand](https://developer.repebble.com/docs/c/Standard_C/Math.md#srand) | | Seed the pseudo-random number generator. | | c | [Memory](https://developer.repebble.com/docs/c/Standard_C/Memory.md) | | Standard memory functions. | | c | [malloc](https://developer.repebble.com/docs/c/Standard_C/Memory.md#malloc) | | Allocates a requested amount of memory. | | c | [calloc](https://developer.repebble.com/docs/c/Standard_C/Memory.md#calloc) | | Allocates space for count objects that are size bytes and fills the memory with bytes of value 0. | | c | [realloc](https://developer.repebble.com/docs/c/Standard_C/Memory.md#realloc) | | Takes the memory allocated at ptr and changes the length of its allocation to the size specified. | | c | [free](https://developer.repebble.com/docs/c/Standard_C/Memory.md#free) | | Frees previously allocated memory. | | c | [size\_t](https://developer.repebble.com/docs/c/Standard_C/Memory.md#size_t) | | size as an unsigned integer | | c | [memcmp](https://developer.repebble.com/docs/c/Standard_C/Memory.md#memcmp) | | Compares the first n bytes of memory regions ptr1 and ptr2. | | c | [memcpy](https://developer.repebble.com/docs/c/Standard_C/Memory.md#memcpy) | | Copies n bytes from src to dest. | | c | [memmove](https://developer.repebble.com/docs/c/Standard_C/Memory.md#memmove) | | Copies n bytes from src to dest by first copying to a temporary area first, allowing dest and src to potentially overlap. | | c | [memset](https://developer.repebble.com/docs/c/Standard_C/Memory.md#memset) | | Sets n bytes to c starting at dest. | | c | [String](https://developer.repebble.com/docs/c/Standard_C/String.md) | | Standard C-string manipulation. | | c | [strcmp](https://developer.repebble.com/docs/c/Standard_C/String.md#strcmp) | | Compares the null terminated strings str1 and str2 to each other. | | c | [strncmp](https://developer.repebble.com/docs/c/Standard_C/String.md#strncmp) | | Compares the null terminated strings str1 and str2 to each other for up to n bytes. | | c | [strcpy](https://developer.repebble.com/docs/c/Standard_C/String.md#strcpy) | | Copies the string in src into dest and null terminates dest. | | c | [strncpy](https://developer.repebble.com/docs/c/Standard_C/String.md#strncpy) | | Copies up to n bytes from the string in src into dest and null terminates dest. | | c | [strcat](https://developer.repebble.com/docs/c/Standard_C/String.md#strcat) | | Concatenates the string in src to the end of the string pointed by dest and null terminates dest. | | c | [strncat](https://developer.repebble.com/docs/c/Standard_C/String.md#strncat) | | Concatenates up to n bytes from the string in src to the end of the string pointed by dest and null terminates dest. | | c | [strlen](https://developer.repebble.com/docs/c/Standard_C/String.md#strlen) | | Calculates the length of a null terminated string. | | c | [Time](https://developer.repebble.com/docs/c/Standard_C/Time.md) | | | | c | [time\_t](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_t) | | time in seconds since the epoch, January 1st 1970 | | c | [TZ\_LEN](https://developer.repebble.com/docs/c/Standard_C/Time.md#TZ_LEN) | | | | c | [SECONDS\_PER\_MINUTE](https://developer.repebble.com/docs/c/Standard_C/Time.md#SECONDS_PER_MINUTE) | | | | c | [MINUTES\_PER\_HOUR](https://developer.repebble.com/docs/c/Standard_C/Time.md#MINUTES_PER_HOUR) | | | | c | [SECONDS\_PER\_HOUR](https://developer.repebble.com/docs/c/Standard_C/Time.md#SECONDS_PER_HOUR) | | | | c | [HOURS\_PER\_DAY](https://developer.repebble.com/docs/c/Standard_C/Time.md#HOURS_PER_DAY) | | | | c | [MINUTES\_PER\_DAY](https://developer.repebble.com/docs/c/Standard_C/Time.md#MINUTES_PER_DAY) | | | | c | [SECONDS\_PER\_DAY](https://developer.repebble.com/docs/c/Standard_C/Time.md#SECONDS_PER_DAY) | | | | c | [strftime](https://developer.repebble.com/docs/c/Standard_C/Time.md#strftime) | | Format the time value at tm according to fmt and place the result in a buffer s of size max. | | c | [localtime](https://developer.repebble.com/docs/c/Standard_C/Time.md#localtime) | | convert the time value pointed at by clock to a struct tm which contains the time adjusted for the local timezone | | c | [gmtime](https://developer.repebble.com/docs/c/Standard_C/Time.md#gmtime) | | convert the time value pointed at by clock to a struct tm which contains the time expressed in Coordinated Universal Time (UTC) | | c | [mktime](https://developer.repebble.com/docs/c/Standard_C/Time.md#mktime) | | convert the broken-down time structure to a timestamp expressed in Coordinated Universal Time (UTC) | | c | [time](https://developer.repebble.com/docs/c/Standard_C/Time.md#time) | | Obtain the number of seconds since epoch. Note that the epoch is not adjusted for Timezones and Daylight Savings. | | c | [difftime](https://developer.repebble.com/docs/c/Standard_C/Time.md#difftime) | | Obtain the number of seconds elapsed between beginning and end represented as a double. | | c | [time\_ms](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_ms) | | Obtain the number of seconds and milliseconds part since the epoch. This is a non-standard C function provided for convenience. | | c | [time\_start\_of\_today](https://developer.repebble.com/docs/c/Standard_C/Time.md#time_start_of_today) | | Return the UTC time that corresponds to the start of today (midnight). | | pebblekit\_js | [addEventListener](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#addEventListener) | | | | pebblekit\_js | [removeEventListener](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#removeEventListener) | | | | pebblekit\_js | [showSimpleNotificationOnPebble](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#showSimpleNotificationOnPebble) | | | | pebblekit\_js | [sendAppMessage](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#sendAppMessage) | | | | pebblekit\_js | [getTimelineToken](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getTimelineToken) | | | | pebblekit\_js | [timelineSubscribe](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#timelineSubscribe) | | | | pebblekit\_js | [timelineUnsubscribe](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#timelineUnsubscribe) | | | | pebblekit\_js | [timelineSubscriptions](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#timelineSubscriptions) | | | | pebblekit\_js | [insertTimelinePin](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#insertTimelinePin) | | | | pebblekit\_js | [deleteTimelinePin](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#deleteTimelinePin) | | | | pebblekit\_js | [getActiveWatchInfo](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getActiveWatchInfo) | | | | pebblekit\_js | [getAccountToken](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getAccountToken) | | | | pebblekit\_js | [getWatchToken](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getWatchToken) | | | | pebblekit\_js | [appGlanceReload](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#appGlanceReload) | | | | pebblekit\_js | [TimelineTopicsCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#TimelineTopicsCallback) | | | | pebblekit\_js | [openURL](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#openURL) | | | | pebblekit\_js | [enumeratePlugins](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#enumeratePlugins) | | | | pebblekit\_js | [subscribeToSource](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#subscribeToSource) | | | | pebblekit\_js | [invokeAction](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#invokeAction) | | | | pebblekit\_js | [sendConfigMessage](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#sendConfigMessage) | | | | pebblekit\_js | [AppGlanceReloadFailureCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppGlanceReloadFailureCallback) | | | | pebblekit\_js | [AppMessageAckCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppMessageAckCallback) | | | | pebblekit\_js | [AppMessageNackCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppMessageNackCallback) | | | | pebblekit\_js | [EventCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#EventCallback) | | | | pebblekit\_js | [TimelineTokenCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#TimelineTokenCallback) | | | | pebblekit\_js | [AppGlanceReloadSuccessCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppGlanceReloadSuccessCallback) | | | | pebblekit\_js | [WatchInfo](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#WatchInfo) | | | | pebblekit\_js | [AppGlanceSlice](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppGlanceSlice) | | | | pebblekit\_android | [com.getpebble.android.kit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit.md) | | | | pebblekit\_android | [com.getpebble.android.kit.util](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util.md) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.FirmwareVersionInfo](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleAckReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.md) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataLogReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.md) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleNackReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.md) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.areAppMessagesSupported](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#areAppMessagesSupported) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.closeAppOnPebble](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#closeAppOnPebble) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.customizeWatchApp](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#customizeWatchApp) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.getWatchFWVersion](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#getWatchFWVersion) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.isDataLoggingSupported](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#isDataLoggingSupported) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.isWatchConnected](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#isWatchConnected) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.registerDataLogReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#registerDataLogReceiver) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.registerPebbleConnectedReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#registerPebbleConnectedReceiver) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.registerPebbleDisconnectedReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#registerPebbleDisconnectedReceiver) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.registerReceivedAckHandler](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#registerReceivedAckHandler) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.registerReceivedDataHandler](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#registerReceivedDataHandler) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.registerReceivedNackHandler](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#registerReceivedNackHandler) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.requestDataLogsForApp](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#requestDataLogsForApp) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.sendAckToPebble](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#sendAckToPebble) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.sendDataToPebble](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#sendDataToPebble) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.sendDataToPebbleWithTransactionId](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#sendDataToPebbleWithTransactionId) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.sendNackToPebble](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#sendNackToPebble) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.startAppOnPebble](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md#startAppOnPebble) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.FirmwareVersionInfo.getMajor](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md#getMajor) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.FirmwareVersionInfo.getMinor](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md#getMinor) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.FirmwareVersionInfo.getPoint](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md#getPoint) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.FirmwareVersionInfo.getTag](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md#getTag) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleAckReceiver.onReceive](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.md#onReceive) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleAckReceiver.receiveAck](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.md#receiveAck) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataLogReceiver.onFinishSession](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md#onFinishSession) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataLogReceiver.onReceive](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md#onReceive) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataLogReceiver.receiveData](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md#receiveData) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataLogReceiver.receiveData](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md#receiveData) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataLogReceiver.receiveData](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md#receiveData) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataReceiver.onReceive](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.md#onReceive) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleDataReceiver.receiveData](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.md#receiveData) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleNackReceiver.onReceive](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.md#onReceive) | | | | pebblekit\_android | [com.getpebble.android.kit.PebbleKit.PebbleNackReceiver.receiveNack](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.md#receiveNack) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants.PebbleAppType.valueOf](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md#valueOf) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants.PebbleAppType.values](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md#values) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants.PebbleDataType.fromByte](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md#fromByte) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants.PebbleDataType.valueOf](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md#valueOf) | | | | pebblekit\_android | [com.getpebble.android.kit.Constants.PebbleDataType.values](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md#values) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.PebbleDictTypeException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException.md) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.TupleOverflowException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException.md) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleTuple.ValueOverflowException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.ValueOverflowException.md) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addBytes](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addBytes) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addInt16](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addInt16) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addInt32](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addInt32) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addInt8](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addInt8) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addString](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addString) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addTuple) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addUint16](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addUint16) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addUint32](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addUint32) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.addUint8](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#addUint8) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.contains](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#contains) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.fromJson](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#fromJson) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.getBytes](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#getBytes) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.getInteger](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#getInteger) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.getString](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#getString) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.getUnsignedIntegerAsLong](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#getUnsignedIntegerAsLong) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.iterator](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#iterator) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.remove](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#remove) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.size](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#size) | | | | pebblekit\_android | [com.getpebble.android.kit.util.PebbleDictionary.toJsonString](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md#toJsonString) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.getCustomLabel](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#getCustomLabel) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.getCustomValue](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#getCustomValue) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.getDistance](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#getDistance) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.getHeartBPM](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#getHeartBPM) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.getPaceInSec](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#getPaceInSec) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.getSpeed](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#getSpeed) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.getTimeInSec](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#getTimeInSec) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.setCustomLabel](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#setCustomLabel) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.setCustomValue](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#setCustomValue) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.setDistance](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#setDistance) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.setHeartBPM](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#setHeartBPM) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.setPaceInSec](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#setPaceInSec) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.setSpeed](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#setSpeed) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.setTimeInSec](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#setTimeInSec) | | | | pebblekit\_android | [com.getpebble.android.kit.util.SportsState.synchronize](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md#synchronize) | | | | pebblekit\_ios | [PBVersionInfoCompletionBlock](https://developer.repebble.com/docs/pebblekit-ios/Blocks/PBVersionInfoCompletionBlock.md) | | | | pebblekit\_ios | [PBPebbleKitLoggingCallback](https://developer.repebble.com/docs/pebblekit-ios/Blocks/PBPebbleKitLoggingCallback.md) | | | | pebblekit\_ios | [NSDictionary+Pebble](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSDictionary%2BPebble/) | | | | pebblekit\_ios | [pb\_pebbleDictionaryData:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSDictionary%2BPebble/#//api/name/pb_pebbleDictionaryData:) | | Serializes the receiver into a Pebble dict. - (nullable NSData \*)pb\_pebbleDictionaryData:(NSError \*\_\_autoreleasing \*)error Parameters error Pointer to an NSError \* that will be set in case there was an error creating the dictionary. Possible error codes: PBErrorCodeDictionaryUnsupportedKeyClass PBErrorCodeDictionaryUnsupportedValueClass PBErrorCodeDictionaryUnsupportedValueClass Discussion The receiver dictionary must comply to the following constraints: The keys must be instances of NSNumber. Their -unsignedLongValue return value will be used as final key. The values must be instances of either NSString, NSData or NSNumber. Use the NSNumber (stdint) category to specify the signedness and width of NSNumber. See Also [NSData(Pebble) pb\_dictionaryFromPebbleDictionaryDataWithError:] Declared In NSDictionary+Pebble.h | | pebblekit\_ios | [NSNumber+PBStandardIntegerExtensions](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/) | | | | pebblekit\_ios | [pb\_uint32Value](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_uint32Value) | | Interprets the receiver as a 32-bits wide, unsigned integer. @property (readonly) uint32\_t pb\_uint32Value Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_uint16Value](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_uint16Value) | | Interprets the receiver as a 16-bits wide, unsigned integer. @property (readonly) uint16\_t pb\_uint16Value Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_uint8Value](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_uint8Value) | | Interprets the receiver as a 8-bits wide, unsigned integer. @property (readonly) uint8\_t pb\_uint8Value Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_int32Value](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_int32Value) | | Interprets the receiver as a 32-bits wide, signed integer. @property (readonly) int32\_t pb\_int32Value Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_int16Value](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_int16Value) | | Interprets the receiver as a 16-bits wide, signed integer. @property (readonly) int16\_t pb\_int16Value Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_int8Value](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_int8Value) | | Interprets the receiver as a 8-bits wide, signed integer. @property (readonly) int8\_t pb\_int8Value Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_float](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_float) | | Gets whether the number that is stored by the receiver should be interpreted as a floating pointer number or not. @property (readonly, getter=pb\_isFloat) BOOL pb\_float Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_signed](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_signed) | | Gets whether the number that is stored by the receiver should be interpreted as a signed integer or not. @property (readonly, getter=pb\_isSigned) BOOL pb\_signed Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_byteWidth](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_byteWidth) | | Gets the width in bytes of the integer that is stored by the receiver. @property (readonly) uint8\_t pb\_byteWidth Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_numberWithUint32:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_numberWithUint32:) | | Creates an NSNumber with a 32-bits wide, unsigned integer. + (NSNumber \*)pb\_numberWithUint32:(uint32\_t)value Parameters value The value for the created number. Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_numberWithUint16:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_numberWithUint16:) | | Creates an NSNumber with a 16-bits wide, unsigned integer. + (NSNumber \*)pb\_numberWithUint16:(uint16\_t)value Parameters value The value for the created number. Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_numberWithUint8:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_numberWithUint8:) | | Creates an NSNumber with a 8-bits wide, unsigned integer. + (NSNumber \*)pb\_numberWithUint8:(uint8\_t)value Parameters value The value for the created number. Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_numberWithInt32:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_numberWithInt32:) | | Creates an NSNumber with a 32-bits wide, signed integer. + (NSNumber \*)pb\_numberWithInt32:(int32\_t)value Parameters value The value for the created number. Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_numberWithInt16:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_numberWithInt16:) | | Creates an NSNumber with a 16-bits wide, signed integer. + (NSNumber \*)pb\_numberWithInt16:(int16\_t)value Parameters value The value for the created number. Declared In NSNumber+stdint.h | | pebblekit\_ios | [pb\_numberWithInt8:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/#//api/name/pb_numberWithInt8:) | | Creates an NSNumber with a 8-bits wide, signed integer. + (NSNumber \*)pb\_numberWithInt8:(int8\_t)value Parameters value The value for the created number. Declared In NSNumber+stdint.h | | pebblekit\_ios | [NSError+Pebble](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSError%2BPebble/) | | | | pebblekit\_ios | [pebbleErrorWithCode:underLyingError:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSError%2BPebble/#//api/name/pebbleErrorWithCode:underLyingError:) | | Convenience method to create an NSError object with error domain and a given PBErrorCode and with an underlying NSError object. + (NSError \*)pebbleErrorWithCode:(PBErrorCode)code underLyingError:(NSError \*\_\_nullable)error Parameters code The error code for which to create the error error The underlying error Declared In PBErrors.h | | pebblekit\_ios | [pebbleErrorWithCode:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSError%2BPebble/#//api/name/pebbleErrorWithCode:) | | Convenience method to create an NSError object with error domain and a given PBErrorCode. + (NSError \*)pebbleErrorWithCode:(PBErrorCode)code Parameters code The error code for which to create the error Declared In PBErrors.h | | pebblekit\_ios | [NSData+Pebble](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSData%2BPebble/) | | | | pebblekit\_ios | [pb\_dictionaryFromPebbleDictionaryDataWithError:](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSData%2BPebble/#//api/name/pb_dictionaryFromPebbleDictionaryDataWithError:) | | Interprets the receiver as Pebble dict data and deserializes it into an NSDictionary. - (nullable NSDictionary \*)pb\_dictionaryFromPebbleDictionaryDataWithError:(NSError \*\_\_autoreleasing \*)error Parameters error Pointer to an NSError \* that will be set after the method has returned in case there was an error. Possible error codes: PBErrorCodeDictionaryInternalConsistency See Also [NSDictionary(Pebble) pb\_pebbleDictionaryData:] Declared In NSDictionary+Pebble.h | | pebblekit\_ios | [PBFirmwareMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md) | | | | pebblekit\_ios | [version](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md#//api/name/version) | | The version of the firmware. @property (nonatomic, readonly) PBFirmwareVersion \*version See Also PBFirmwareVersion Declared In PBFirmwareMetadata.h | | pebblekit\_ios | [isRecoveryFirmware](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md#//api/name/isRecoveryFirmware) | | YES if the firmware is a recovery firmware, NO if it is a regular firmware. @property (nonatomic, readonly) BOOL isRecoveryFirmware Declared In PBFirmwareMetadata.h | | pebblekit\_ios | [hardwarePlatform](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md#//api/name/hardwarePlatform) | | The hardware platform variant with which the firmware is compatible. @property (nonatomic, readonly) FirmwareMetadataPlatform hardwarePlatform Declared In PBFirmwareMetadata.h | | pebblekit\_ios | [hardwarePlatformToString:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md#//api/name/hardwarePlatformToString:) | | Converts a FirmwareMetadataPlatform value to a string. + (NSString \*)hardwarePlatformToString:(FirmwareMetadataPlatform)hardwarePlatform Parameters hardwarePlatform The hardware platform value to convert Return Value A string of the hardware platform Declared In PBFirmwareMetadata.h | | pebblekit\_ios | [stringToHardwarePlatform:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md#//api/name/stringToHardwarePlatform:) | | Converts an NSString hardware platform string to the corresponding FirmwareMetadataPlatform value. + (FirmwareMetadataPlatform)stringToHardwarePlatform:(NSString \*)hardwarePlatformString Parameters hardwarePlatformString The string representing the hardware platform. Return Value The hardware platform enum value Declared In PBFirmwareMetadata.h | | pebblekit\_ios | [PBResourceMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBResourceMetadata.md) | | | | pebblekit\_ios | [timestamp](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBResourceMetadata.md#//api/name/timestamp) | | The version of the resources as the Epoch timestamp of the Git commit. @property (nonatomic, readonly) uint32\_t timestamp Declared In PBResourceMetadata.h | | pebblekit\_ios | [crc](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBResourceMetadata.md#//api/name/crc) | | The checksum of the resources. @property (nonatomic, readonly) uint32\_t crc Declared In PBResourceMetadata.h | | pebblekit\_ios | [PBPebbleCentral](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md) | | | | pebblekit\_ios | [connectedWatches](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/connectedWatches) | | The watches that are currently connected. Do not cache the array because it can change over time. @property (nonatomic, readonly, copy) NSArray \*connectedWatches Declared In PBPebbleCentral.h | | pebblekit\_ios | [registeredWatches](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/registeredWatches) | | The watches that are stored in the user preferences of the application. @property (nonatomic, readonly, copy) NSArray \*registeredWatches Declared In PBPebbleCentral.h | | pebblekit\_ios | [delegate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/delegate) | | The central’s delegate. @property (nonatomic, readwrite, weak) id \_\_nullable delegate Declared In PBPebbleCentral.h | | pebblekit\_ios | [appUUID](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/appUUID) | | Identifier of the watch application this companion app communicates with. @property (nonatomic, copy) NSUUID \*appUUID Discussion The identifier is used to make sure that app message and data logging communications arrive to the right companion watch app in the watch (and not to another app). For most app message methods there is are two variants: one that does not take an UUID parameter and one that does (for example, -appMessagesAddReceiveUpdateHandler: vs -appMessagesAddReceiveUpdateHandler:withUUID:). The methods that do not take an UUID, will use the UUID as set prior to this property.Note: The UUID needs to be set before using either app message or data logging. Declared In PBPebbleCentral.h | | pebblekit\_ios | [appUUIDs](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/appUUIDs) | | The list of App-UUIDs this PebbleCentral wants to talk to. @property (nonatomic, copy) NSSet \*appUUIDs See Also - addAppUUID: Declared In PBPebbleCentral.h | | pebblekit\_ios | [addAppUUID:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/addAppUUID:) | | Registers a new App-UUID with appUUIDs. - (void)addAppUUID:(NSUUID \*)appUUID Parameters appUUID The app UUID to register. See Also @property appUUIDs Declared In PBPebbleCentral.h | | pebblekit\_ios | [run](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/run) | | Registers and announces internal Bluetooth services. Might cause a dialog to allow this app to talk to other devices. - (void)run Declared In PBPebbleCentral.h | | pebblekit\_ios | [isMobileAppInstalled](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/isMobileAppInstalled) | | Determines if the Pebble iOS app is installed in the device. - (BOOL)isMobileAppInstalled Return Value YES if the Pebble iOS app is installed, NO if it is not installed. Discussion Note: Since iOS 9.0 you have to add “pebble” to LSApplicationQueriesSchemes in your application Info.plist or this method will return NO all the time. Declared In PBPebbleCentral.h | | pebblekit\_ios | [installMobileApp](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/installMobileApp) | | Redirects to Pebble in the App Store, so the user can install the app. - (void)installMobileApp Declared In PBPebbleCentral.h | | pebblekit\_ios | [unregisterAllWatches](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/unregisterAllWatches) | | Wipes out the data associated with the registered watches, that is stored on the phone. - (void)unregisterAllWatches Declared In PBPebbleCentral.h | | pebblekit\_ios | [lastConnectedWatch](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/lastConnectedWatch) | | Returns the most recently connected watch from the registeredWatches array. - (PBWatch \*\_\_nullable)lastConnectedWatch Declared In PBPebbleCentral.h | | pebblekit\_ios | [dataLoggingServiceForAppUUID:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/dataLoggingServiceForAppUUID:) | | Returns the DataLoggingService for a (previously registered) appUUID - (PBDataLoggingService \*\_\_nullable)dataLoggingServiceForAppUUID:(NSUUID \*)appUUID Parameters appUUID The app UUID to recover the data logging service. Declared In PBPebbleCentral.h | | pebblekit\_ios | [defaultCentral](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/defaultCentral) | | The default central singleton instance. + (instancetype)defaultCentral Return Value The default central singleton instance. Declared In PBPebbleCentral+DefaultCentral.h | | pebblekit\_ios | [hasValidAppUUID](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/hasValidAppUUID) | | Indicates if the central has been correctly configured with an app UUID. (Deprecated: Use appUUID != nil instead.) - (BOOL)hasValidAppUUID Declared In PBPebbleCentral+Legacy.h | | pebblekit\_ios | [setDebugLogsEnabled:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/setDebugLogsEnabled:) | | Enables or disables debug logging for PebbleKit. (Deprecated: Use +[PBPebbleKitLogging setLogLevel:] instead.) + (void)setDebugLogsEnabled:(BOOL)logsEnabled Parameters logsEnabled Whether logging should be enabled or not. Declared In PBPebbleCentral+Legacy.h | | pebblekit\_ios | [setLogLevel:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/setLogLevel:) | | Configures which events should be logged. (Deprecated: Use +[PBPebbleKitLogging setLogLevel:] instead.) + (void)setLogLevel:(PBPebbleKitLogLevel)logLevel Parameters logLevel One of the values in PBPebbleKitLogLevel, which will be the maximum level that will be logged. You can use PBPebbleKitLogLevelOff to disable all logging. Declared In PBPebbleCentral+Legacy.h | | pebblekit\_ios | [dataLoggingService](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/dataLoggingService) | | Use dataLoggingServiceForAppUUID: instead. (Deprecated: Use dataLoggingServiceForAppUUID: instead.) @property (nonatomic, readonly) PBDataLoggingService \*dataLoggingService Declared In PBPebbleCentral+Legacy.h | | pebblekit\_ios | [PBDataLoggingSessionMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md) | | | | pebblekit\_ios | [tag](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/tag) | | A tag associated with the session. @property (nonatomic, readonly) uint32\_t tag Declared In PBDataLoggingService.h | | pebblekit\_ios | [timestamp](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/timestamp) | | The timestamp of the moment the session was created by the watchapp. @property (nonatomic, readonly) uint32\_t timestamp Declared In PBDataLoggingService.h | | pebblekit\_ios | [type](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/type) | | The type of data stored in this session. @property (nonatomic, readonly) PBDataLoggingType type Declared In PBDataLoggingService.h | | pebblekit\_ios | [itemSize](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/itemSize) | | The size of a data item. @property (nonatomic, readonly) uint16\_t itemSize Declared In PBDataLoggingService.h | | pebblekit\_ios | [serialNumber](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/serialNumber) | | The serial number of the watch that created the sessions. @property (nonatomic, copy, readonly) NSString \*serialNumber Declared In PBDataLoggingService.h | | pebblekit\_ios | [metadataWithTag:timestamp:type:itemSize:serialNumber:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/metadataWithTag:timestamp:type:itemSize:serialNumber:) | | Creates a new data logging session metadata object, given all its property values. This method is provided to create a metadata object that can be used to compare it with a metadata object that is passed into one of the PBDataLoggingServiceDelegate methods. + (instancetype)metadataWithTag:(uint32\_t)tag timestamp:(uint32\_t)timestamp type:(PBDataLoggingType)type itemSize:(uint16\_t)itemSize serialNumber:(NSString \*)serialNumber Parameters tag The tag associated with the session timestamp The timestamp of the creation of the session type The type of data stored in the session itemSize The size of a data item serialNumber The serial number of the watch that created the session Return Value A session metadata object with the specified information Declared In PBDataLoggingService.h | | pebblekit\_ios | [isEqual:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/isEqual:) | | Tests the equality of the recipient and the given object. - (BOOL)isEqual:(nullable id)object Parameters object The object to check against. Return Value YES if all the property values of the receiver are equal to the property values of the other object. Discussion This method can be used to compare two sessions’ metadata objects to check whether they are referring to the same session or not. Declared In PBDataLoggingService.h | | pebblekit\_ios | [isEqualToDataLoggingSessionMetadata:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md#//api/name/isEqualToDataLoggingSessionMetadata:) | | Tests the equality of two data logging sessions’ metadata objects. - (BOOL)isEqualToDataLoggingSessionMetadata:(PBDataLoggingSessionMetadata \*)sessionMetadata Parameters sessionMetadata The session metadata to compare against. Return Value YES if all the property values of the receiver are equal to the property values of the given session. Discussion This method can be used to compare two sessions’ metadata objects to check whether they are referring to the same session or not.Note: Equivalent to [PBDataLoggingSessionMetadata isEqual:] Declared In PBDataLoggingService.h | | pebblekit\_ios | [PBWatch](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md) | | | | pebblekit\_ios | [central](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/central) | | The central that is managing this watch. @property (nonatomic, weak, readonly) PBPebbleCentral \*central Discussion This property is KVO-compliant. Declared In PBWatch.h | | pebblekit\_ios | [isNew](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/isNew) | | YES if the receiver it’s the first time that watch has been seen. - (BOOL)isNew Discussion This property is KVO-compliant. Declared In PBWatch.h | | pebblekit\_ios | [connected](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/connected) | | YES if the receiver is connected and NO if the receiver is disconnected. @property (nonatomic, assign, readonly, getter=isConnected) BOOL connected Discussion This property is KVO-compliant. Declared In PBWatch.h | | pebblekit\_ios | [name](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/name) | | The human-friendly name of the receiver. This is the same name as the user will see in the iOS Bluetooth Settings. @property (nonatomic, copy, readonly) NSString \*name Declared In PBWatch.h | | pebblekit\_ios | [serialNumber](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/serialNumber) | | The serial number of the receiver. @property (nonatomic, copy, readonly) NSString \*serialNumber Declared In PBWatch.h | | pebblekit\_ios | [versionInfo](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/versionInfo) | | The versionInfo of the receiver. @property (nonatomic, strong, readonly) PBVersionInfo \*versionInfo See Also PBVersionInfo Declared In PBWatch.h | | pebblekit\_ios | [delegate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/delegate) | | The delegate of the watch that will be notified of disconnections and errors. @property (nonatomic, weak) id \_\_nullable delegate See Also PBWatchDelegate Declared In PBWatch.h | | pebblekit\_ios | [userInfo](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/userInfo) | | The userInfo property can be used to associate application specific data with the watch. Note that the application itself is responsible for persisting the information if neccessary. @property (nonatomic, strong) id \_\_nullable userInfo Declared In PBWatch.h | | pebblekit\_ios | [lastConnectedDate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/lastConnectedDate) | | The date when the watch was last known to be connected. This date will be updated automatically when the watch connects and disconnects. While the watch is being connected, this date will not be updated. @property (nonatomic, strong, readonly) NSDate \*lastConnectedDate Declared In PBWatch.h | | pebblekit\_ios | [friendlyDescription](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/friendlyDescription) | | Developer-friendly debugging description of the watch. - (NSString \*)friendlyDescription Return Value developer-friendly summary of the receiver, including software and hardware version information, if available. Declared In PBWatch.h | | pebblekit\_ios | [releaseSharedSession](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/releaseSharedSession) | | Releases the shared session to the watch (if one exists). (Deprecated: Remove usages of this method. The implementation is empty.) - (void)releaseSharedSession Discussion Depending on availability a per-app dedicated Bluetooth LE based session (CoreBluetooth.framework) will be used to talk to the watch. In other cases a Bluetooth Classic based session (ExternalAccessory.framework) will be used that is shared between all 3rd party iOS apps. Once the user is done using the app/watch-integration, the shared sessions has to be released using this method so it can be used by other apps. Declared In PBWatch.h | | pebblekit\_ios | [golfGetIsSupported:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/golfGetIsSupported:) | | Queries the watch whether Golf Messages are supported. - (void)golfGetIsSupported:(void ( ^ ) ( PBWatch \*watch , BOOL isGolfSupported ))fetchedBlock Parameters fetchedBlock The block that will be called when the inquiry has finished. The block will be called asynchronously on the queue that was originally used when calling this method. watch: The watch on which the query was performed. isGolfSupported: YES if Golf Messages are supported, NO if not. Discussion Must be called from the main thread. Declared In PBWatch+Golf.h | | pebblekit\_ios | [golfAppLaunch:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/golfAppLaunch:) | | Send a command to launch the golf app on the watch that the receiver represents. - (void)golfAppLaunch:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters onSent The handler that will be called when the launch command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. Declared In PBWatch+Golf.h | | pebblekit\_ios | [golfAppKill:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/golfAppKill:) | | Send a command to kill the golf app on the watch that the receiver represents. - (void)golfAppKill:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters onSent The handler that will be called when the kill command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. Declared In PBWatch+Golf.h | | pebblekit\_ios | [golfAppUpdate:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/golfAppUpdate:onSent:) | | Sends an update to the golf app on the watch that the receiver represents. Must be called from the main thread. - (void)golfAppUpdate:(NSDictionary \*)update onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters update The update to send. Use one or more keys from PBGolfFrontKey, PBGolfMidKey, PBGolfBackKey, PBGolfHoleKey, or PBGolfParKey. Note that the value for each key MUST be of NSString type. onSent The handler that will be called when the update has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Declared In PBWatch+Golf.h | | pebblekit\_ios | [golfAppAddReceiveUpdateHandler:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/golfAppAddReceiveUpdateHandler:) | | Add a receive handler for incoming Golf updates that are send by the Golf watch application. - (id)golfAppAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch \*watch , GolfAppCommand command ))onReceive Parameters onReceive The block that will be called every time a new update message arrives. watch: The receiver of the update. command: The command as sent by the watch. Return Value An opaque handle object representing the installed receive handler, that can be used in golfAppRemoveUpdateHandler: Discussion Must be called from the main thread. See Also GolfAppCommand - golfAppRemoveUpdateHandler: Declared In PBWatch+Golf.h | | pebblekit\_ios | [golfAppRemoveUpdateHandler:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/golfAppRemoveUpdateHandler:) | | Removes a receive handler that was previously installed using golfAppAddReceiveUpdateHandler: - (void)golfAppRemoveUpdateHandler:(id)opaqueHandle Parameters opaqueHandle The handle object as returned by golfAppAddReceiveUpdateHandler: Discussion Must be called from the main thread. See Also - golfAppAddReceiveUpdateHandler: Declared In PBWatch+Golf.h | | pebblekit\_ios | [golfSetTitle:icon:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/golfSetTitle:icon:onSent:) | | Assigns a custom title and icon to the golf app on the watch. - (void)golfSetTitle:(NSString \*)title icon:(UIImage \*)icon onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters title The custom title (max. 20 bytes of UTF-8 string) icon The custom icon (max. 32x32 pixels, black/white only) onSent The handler that will be called when the title and icon have been set or timed out. watch: the recipient of the title and icon. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. It is recommended to perform this as the first call after golfGetIsSupported: to avoid changing the title and icon while it is being displayed in the menu as to avoid confusion. Declared In PBWatch+Golf.h | | pebblekit\_ios | [pingWithCookie:onPong:onTimeout:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/pingWithCookie:onPong:onTimeout:) | | Sends a ping to the watch. - (void)pingWithCookie:(uint32\_t)cookie onPong:(void ( ^ \_\_nullable ) ( PBWatch \*watch , uint32\_t cookie ))onPong onTimeout:(void ( ^ \_\_nullable ) ( PBWatch \*watch , uint32\_t cookie ))onTimeout Parameters cookie A number identifying the ping. onPong The block handler that will be called when the “pong” reply from the watch has been received. onTimeout The block handler that will be called when the watch failed to reply in time. Discussion Must be called from the main thread. Declared In PBWatch+Ping.h | | pebblekit\_ios | [sportsAppSetLabel:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppSetLabel:onSent:) | | Send a command to the sports app on the watch that the receiver represents, to set the preferred data label (either PACE or SPEED) and corresponding units. (Deprecated: Prefer using [PBSportsUpdate pace] or [PBSportsUpdate speed].) - (void)sportsAppSetLabel:(BOOL)isPace onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters isPace YES to request the pace label or NO to request the speed label. onSent The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppSetMetric:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppSetMetric:onSent:) | | Send a command to the sports app on the watch that the receiver represents, to set the preferred unit system, either metric or imperial. (Deprecated: Prefer using [PBWatch(Sports) sportsAppSetUnitSystem:onSent:]) - (void)sportsAppSetMetric:(BOOL)isMetric onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters isMetric YES to request metric units or NO to request imperial units. onSent The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. Declared In PBWatch+Sports.h | | pebblekit\_ios | [appMessagesGetIsSupported:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesGetIsSupported:) | | Queries the watch whether AppMessages are supported. - (void)appMessagesGetIsSupported:(void ( ^ ) ( PBWatch \*watch , BOOL isAppMessagesSupported ))fetchedBlock Parameters fetchedBlock The block that will be called when the inquiry has finished. The block will be called asynchronously on the queue that was originally used when calling this method. watch: The watch on which the query was performed. isAppMessagesSupported: YES if AppMessages are supported, NO if not. Discussion Must be called from the main thread. Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesPushUpdate:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesPushUpdate:onSent:) | | Pushes an update to the watch application with UUID as set using [PBPebbleCentral setAppUUID:]. - (void)appMessagesPushUpdate:(NSDictionary \*)dictionary onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSDictionary \*update , NSError \*\_\_nullable error ))onSent Parameters dictionary Contains the key/value pairs to update. The dictionary can only contain items with an NSNumber key and only contain NSString, NSNumber or NSData values. Use the methods in the NSNumber (stdint) category to create NSNumbers with specific types standard integer types. onSent The block that will be called when the message was accepted, rejected or timed out. watch: The watch to which the update was sent. update: The dictionary that was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. Discussion Must be called from the main thread. Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesPushUpdate:withUUID:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesPushUpdate:withUUID:onSent:) | | Pushes an update to the watch application with the specified UUID. - (void)appMessagesPushUpdate:(NSDictionary \*)dictionary withUUID:(NSUUID \*)appUUID onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSDictionary \*update , NSError \*\_\_nullable error ))onSent Parameters dictionary Contains the key/value pairs to update. The dictionary can only contain items with an NSNumber key and only contain NSString, NSNumber or NSData values. Use the methods in the NSNumber (stdint) category to create NSNumbers with specific types standard integer types. appUUID The UUID of the watchapp to which the update should be sent. onSent The block that will be called when the message was accepted, rejected or timed out. watch: The watch to which the update was sent. update: The dictionary that was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. Discussion Must be called from the main thread. Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesAddReceiveUpdateHandler:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesAddReceiveUpdateHandler:) | | Add a receive handler for incoming updates that are send by the watch application with UUID as set using [PBPebbleCentral setAppUUID:]. - (id)appMessagesAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch \*watch , NSDictionary \*update ))onReceive Parameters onReceive The block that will be called every time a new update message arrives. watch: The watch that has sent the update. update: The dictionary containing the values sent by the watch. Return Value An opaque handle object representing the installed receive handler, that can be used in appMessagesRemoveUpdateHandler: Discussion Must be called from the main thread. See Also - appMessagesRemoveUpdateHandler: Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesAddReceiveUpdateHandler:withUUID:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesAddReceiveUpdateHandler:withUUID:) | | Add a receive handler for incoming updates that are send by the watch application with the specified UUID. - (id)appMessagesAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch \*watch , NSDictionary \*update ))onReceive withUUID:(NSUUID \*)appUUID Parameters onReceive The block that will be called every time a new update message arrives. You should always return YES so than an ACK is sent to the watch. watch: The watch that has sent the update. update: The dictionary containing the values sent by the watch. appUUID The UUID of the watchapp for which sent messages should be handled by the onReceive block. Return Value An opaque handle object representing the installed receive handler, that can be used in appMessagesRemoveUpdateHandler: Discussion Must be called from the main thread. See Also - appMessagesRemoveUpdateHandler: Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesRemoveUpdateHandler:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesRemoveUpdateHandler:) | | Removes a receive handler that was previously installed using appMessagesAddReceiveUpdateHandler: - (void)appMessagesRemoveUpdateHandler:(id)opaqueHandle Parameters opaqueHandle The handle object as returned by appMessagesAddReceiveUpdateHandler: Discussion Must be called from the main thread. See Also - appMessagesAddReceiveUpdateHandler: Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesLaunch:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesLaunch:) | | Sends a command to launch the watch application with UUID as set using [PBPebbleCentral setAppUUID:] - (void)appMessagesLaunch:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters onSent The block that will be called after the launch command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. Discussion Must be called from the main thread. Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesLaunch:withUUID:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesLaunch:withUUID:) | | Sends a command to launch the watch application with the specified UUID. - (void)appMessagesLaunch:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent withUUID:(NSUUID \*)appUUID Parameters onSent The block that will be called after the launch command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. appUUID The UUID of the watch application to launch. Discussion Must be called from the main thread. Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesKill:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesKill:) | | Sends a command to kill the watch application with UUID as set using [PBPebbleCentral setAppUUID:]. - (void)appMessagesKill:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters onSent The block that will be called after the kill command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. Discussion Must be called from the main thread. Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [appMessagesKill:withUUID:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/appMessagesKill:withUUID:) | | Sends a command to kill the watch application with the specified UUID. - (void)appMessagesKill:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent withUUID:(NSUUID \*)appUUID Parameters onSent The block that will be called after the kill command has been sent to the watch. watch: The watch to which the command was sent. error: If there was a problem, this will contain information about the underlying problem. See PBErrorCode for error codes. appUUID The UUID of the watch application to launch. Discussion Must be called from the main thread. Declared In PBWatch+AppMessages.h | | pebblekit\_ios | [closeSession:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/closeSession:) | | Closes the communication session with the watch. (Deprecated: Use [PBWatch releaseSharedSession]) - (void)closeSession:(void ( ^ \_\_nullable ) ( void ))onDone Parameters onDone Callback block that will be called after the closing of the session has completed. If there is no open session, the onDone block will (also) be executed asynchronously on the calling queue. Discussion Since there is only one, shared session for all 3rd party iOS apps, an app should close the session after the user is done using the app/watch-integration, so it can be used by other apps. The communication session is implicitely opened automatically when needed. Declared In PBWatch+Legacy.h | | pebblekit\_ios | [sportsGetIsSupported:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsGetIsSupported:) | | Queries the watch whether Sports Messages are supported. - (void)sportsGetIsSupported:(void ( ^ ) ( PBWatch \*watch , BOOL isSportsSupported ))fetchedBlock Parameters fetchedBlock The block that will be called when the inquiry has finished. The block will be called asynchronously on the queue that was originally used when calling this method. watch: the recipient of the query. isSportsSupported: YES if Sports API is supported, NO otherwise. Discussion Must be called from the main thread. Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppLaunch:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppLaunch:) | | Send a command to launch the sports app on the watch that the receiver represents. - (void)sportsAppLaunch:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters onSent The handler that will be called when the launch command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread, and before sportsAppSetMetric:onSent: or sportsAppUpdate:onSent:. Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppKill:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppKill:) | | Send a command to kill the sports app on the watch that the receiver represents. - (void)sportsAppKill:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters onSent The handler that will be called when the kill command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppSetUnitSystem:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppSetUnitSystem:onSent:) | | Send a command to the sports app on the watch that the receiver represents, to set the preferred unit system. - (void)sportsAppSetUnitSystem:(PBUnitSystem)unitSystem onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters unitSystem PBUnitSystemMetric to request metric units or PBUnitSystemImperial to request imperial units. onSent The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppSetActivityState:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppSetActivityState:onSent:) | | Send a command to the sports app on the watch that the receiver represents, to set the state of the sports activity. Currently only SportsAppActivityStateRunning and SportsAppActivityStatePaused are supported. - (void)sportsAppSetActivityState:(SportsAppActivityState)state onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters state The new sports activity state onSent The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppUpdate:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppUpdate:onSent:) | | Sends the update to the sports app on the watch that the receiver represents. - (void)sportsAppUpdate:(NSDictionary \*)update onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters update The update to send. onSent The handler that will be called when the unit command has been sent or timed out (after 1.5 secs). watch: the recipient of the command. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread. The valid keys and values for the dictionary are the following: PBSportsTimeKey: the string representing the time. The value must be an NSString. The maximum number of characters is about 8. PBSportsDistanceKey: the string representing the distance. The value must be an NSString. The maximum number of characters is about 5. PBSportsDataKey: the string representing the pace or the speed. The value must be an NSString. The maximum number of characters is about 5. PBSportsActivityStateKey: the state of the activity. The value must be an NSNumber created using +[NSNumber(stdint) pb\_numberWithUint8:]. The value must be one of the values from the enum SportsAppActivityState. PBSportsIsPaceKey: whether the value sent in PBSportsDataKey is the pace (use value 1) or the speed (use value 0). You don’t need to continously send this value. The value must be an NSNumber created using +[NSNumber(stdin) pb\_numberWithUint8:]. PBSportsHeartBPMKey: the current heart rate in beats per minute. The value must be an NSNumber created using [NSNumber(stdin) pb\_numberWithUint8:]. PBSportsCustomLabelKey: the string to show as a custom label. The value must be an NSString. The maximum number of characters is about 10. The string will be transformed into upper case before being sent to the watch. PBSportsCustomValueKey: the string to show as a custom value. The value must be an NSString. The maximum number of characters is about 8. Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppAddReceiveUpdateHandler:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppAddReceiveUpdateHandler:) | | Add a receive handler for incoming Sports updates that are send by the Sports watch application. - (id)sportsAppAddReceiveUpdateHandler:(BOOL ( ^ ) ( PBWatch \*watch , SportsAppActivityState state ))onReceive Parameters onReceive The block that will be called every time a new update message arrives. watch: The watch that has sent the update. state: The new sports activity state as set by the watch. Return Value An opaque handle object representing the installed receive handler, that can be used in sportsAppRemoveUpdateHandler:. Discussion Must be called from the main thread. See Also SportsAppActivityState - sportsAppRemoveUpdateHandler: Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsAppRemoveUpdateHandler:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsAppRemoveUpdateHandler:) | | Removes a receive handler that was previously installed using sportsAppAddReceiveUpdateHandler:. - (void)sportsAppRemoveUpdateHandler:(id)opaqueHandle Parameters opaqueHandle The handle object as returned by sportsAppAddReceiveUpdateHandler: Discussion Must be called from the main thread. See Also - sportsAppAddReceiveUpdateHandler: Declared In PBWatch+Sports.h | | pebblekit\_ios | [sportsSetTitle:icon:onSent:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md#//api/name/sportsSetTitle:icon:onSent:) | | Assigns a custom title and icon to the sports app on the watch. - (void)sportsSetTitle:(NSString \*)title icon:(UIImage \*)icon onSent:(void ( ^ \_\_nullable ) ( PBWatch \*watch , NSError \*\_\_nullable error ))onSent Parameters title The custom title (max. 20 bytes of UTF-8 string) icon The custom icon (max. 32x32 pixels, black/white only). This image cannot be nil. onSent The handler that will be called when the title and icon have been set or timed out. watch: the recipient of the custom title and icon. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion Must be called from the main thread.It is recommended to perform this as the first call after sportsGetIsSupported: to avoid changing the title and icon while it is being displayed in the menu as to avoid confusion. Declared In PBWatch+Sports.h | | pebblekit\_ios | [PBPebbleKitLogging](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging.md) | | | | pebblekit\_ios | [setLogLevel:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging.md#//api/name/setLogLevel:) | | Configures which events should be logged. + (void)setLogLevel:(PBPebbleKitLogLevel)logLevel Parameters logLevel One of the values in PBPebbleKitLogLevel, which will be the maximum level that will be logged. You can use PBPebbleKitLogLevelOff to disable all logging. Declared In PBLog+Public.h | | pebblekit\_ios | [setLoggingCallback:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging.md#//api/name/setLoggingCallback:) | | Sets a new logging callback that will be invoked for each of the log records emitted by PebbleKit. + (void)setLoggingCallback:(nullable PBPebbleKitLoggingCallback)loggingCallback Parameters loggingCallback A block that will be invoked for each of the log records emitted by PebbleKit. Set this value to nil to return to the default NSLog logging. Declared In PBLog+Public.h | | pebblekit\_ios | [new](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging.md#//api/name/new) | | You should not create instances of this type. + (instancetype)new Discussion Note: You should not create instances of this type. Declared In PBLog+Public.h | | pebblekit\_ios | [init](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging.md#//api/name/init) | | You should not create instances of this type. - (instancetype)init Discussion Note: You should not create instances of this type. Declared In PBLog+Public.h | | pebblekit\_ios | [PBSemanticVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md) | | | | pebblekit\_ios | [majorVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/majorVersion) | | The version major number @property (nonatomic, assign, readonly) NSUInteger majorVersion Declared In PBSemanticVersion.h | | pebblekit\_ios | [minorVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/minorVersion) | | The version minor number @property (nonatomic, assign, readonly) NSUInteger minorVersion Declared In PBSemanticVersion.h | | pebblekit\_ios | [revisionVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/revisionVersion) | | The version revision number @property (nonatomic, assign, readonly) NSUInteger revisionVersion Declared In PBSemanticVersion.h | | pebblekit\_ios | [suffix](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/suffix) | | The version suffix string. @property (nonatomic, copy, readonly) NSString \*suffix Discussion Note: If a version tag string does not contain a suffix, it will be set to an empty string. The release suffix is not used in the compare: or -isEqual: methods. Declared In PBSemanticVersion.h | | pebblekit\_ios | [initWithVersionString:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/initWithVersionString:) | | Returns a new instance by parsing the input. - (nullable instancetype)initWithVersionString:(NSString \*)versionString Parameters versionString Expected format: major.minor.revision-sffix. Example: “2.0.1-rc2” If a parsing error occurs, nil will be returned. Return Value A version or nil if some error happen. Declared In PBSemanticVersion.h | | pebblekit\_ios | [initWithMajor:minor:revision:suffix:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/initWithMajor:minor:revision:suffix:) | | Returns a new instance by using the given components. - (instancetype)initWithMajor:(NSUInteger)major minor:(NSUInteger)minor revision:(NSUInteger)revision suffix:(nullable NSString \*)suffix Parameters major The version major number. minor The version minor number. revision The version revision number. suffix The version suffix. Can be nil. Declared In PBSemanticVersion.h | | pebblekit\_ios | [compare:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/compare:) | | Compares the receiver to another version object. - (NSComparisonResult)compare:(PBSemanticVersion \*)aVersion Parameters aVersion The version to compare with the receiver. Return Value NSOrderedAscending if the value of aVersion is greater than the receiver’s, NSOrderedSame if they’re equal, and NSOrderedDescending if the value of aVersion is less than the receiver’s. Declared In PBSemanticVersion.h | | pebblekit\_ios | [isEqualOrNewer:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/isEqualOrNewer:) | | Returns if the the receiver is equal or newer than the given version. - (BOOL)isEqualOrNewer:(PBSemanticVersion \*)other Parameters other Version number to compare against. Return Value YES if the receiver is equal or newer. NO otherwise. Declared In PBSemanticVersion.h | | pebblekit\_ios | [isNewer:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md#//api/name/isNewer:) | | Returns if the receiver is strictly newer than the given version. - (BOOL)isNewer:(PBSemanticVersion \*)other Parameters other Version number to compare against. Return Value YES if the receiver is stricly newer. NO otherwise. Declared In PBSemanticVersion.h | | pebblekit\_ios | [PBBitmap](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md) | | | | pebblekit\_ios | [pixelData](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md#//api/name/pixelData) | | The pixel data of the bitmap. This corresponds to the data as pointed to by GBitmap’s addr field. @property (nonatomic, readonly, strong) NSData \*pixelData Declared In PBBitmap.h | | pebblekit\_ios | [rowSizeBytes](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md#//api/name/rowSizeBytes) | | The number of bytes per row. Always a multiple of 4 bytes. @property (nonatomic, readonly, assign) uint16\_t rowSizeBytes Declared In PBBitmap.h | | pebblekit\_ios | [infoFlags](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md#//api/name/infoFlags) | | Bitfield of metadata flags. This corresponds to GBitmap’s info\_flags field. @property (nonatomic, readonly, assign) uint16\_t infoFlags Declared In PBBitmap.h | | pebblekit\_ios | [bounds](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md#//api/name/bounds) | | The box of bits inside pixelData, that contains the actual image data to use. This corresponds to GBitmap’s bounds field. @property (nonatomic, readonly, assign) GRect bounds Declared In PBBitmap.h | | pebblekit\_ios | [pebbleBitmapWithUIImage:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md#//api/name/pebbleBitmapWithUIImage:) | | Factory method to create a PBBitmap from a UIImage instance. + (PBBitmap \*)pebbleBitmapWithUIImage:(UIImage \*)image Parameters image The UIImage from which to create the PBBitmap Declared In PBBitmap.h | | pebblekit\_ios | [PBDataLoggingService](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService.md) | | | | pebblekit\_ios | [delegate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService.md#//api/name/delegate) | | The delegate that has the responsility of handling callbacks from the data logging service. @property (atomic, readwrite, weak) id delegate Declared In PBDataLoggingService.h | | pebblekit\_ios | [setDelegateQueue:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService.md#//api/name/setDelegateQueue:) | | Sets the queue on which delegate methods will be executed. Callbacks are intended to be processed in the order as they come in, so it you must use a serial queue. If set to nil (default) the main queue is used. - (void)setDelegateQueue:(dispatch\_queue\_t)delegateQueue Parameters delegateQueue The queue on which the delegate methods will be executed. Declared In PBDataLoggingService.h | | pebblekit\_ios | [pollForData](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService.md#//api/name/pollForData) | | Query the latestConnectedWatch for data logging data. (Deprecated: Use pollForDataFromWatch:) - (void)pollForData Declared In PBDataLoggingService.h | | pebblekit\_ios | [pollForDataFromWatch:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService.md#//api/name/pollForDataFromWatch:) | | Query the watch for data logging data. - (void)pollForDataFromWatch:(PBWatch \*)watch Parameters watch The watch to query data from. Declared In PBDataLoggingService.h | | pebblekit\_ios | [PBSportsUpdate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md) | | | | pebblekit\_ios | [time](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/time) | | The current time in seconds. @property (nonatomic) NSTimeInterval time Discussion The possible range is currently limited from -35999 to 35999, inclusive (±9h 59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Hours, minutes and seconds will be separated by colons. The hours value will only appear if the value is more than 1 hour. The decimal part will be discarded for the presentation. Declared In PBWatch+Sports.h | | pebblekit\_ios | [distance](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/distance) | | The current distance in kilometers or miles. @property (nonatomic) float distance Discussion The possible range is currently limited from -99.9 to 99.9, inclusive. Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a decimal number in the UI. The decimal part will be rounded to one digit. The unit of distance is dependent on the current unit setting. See Also [PBWatch sportsAppSetMetric:onSent:] Declared In PBWatch+Sports.h | | pebblekit\_ios | [pace](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/pace) | | The current pace in seconds per kilometer or seconds per mile. @property (nonatomic) NSTimeInterval pace Discussion The possible range is currently limited from -3599 to 3599, inclusive (±59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Minutes and seconds will be separated by colons. The decimal part will be discarded for the presentation. Currently pace and speed cannot be presented at the same time. Setting speed will discard the value set through pace. Declared In PBWatch+Sports.h | | pebblekit\_ios | [speed](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/speed) | | The current speed in kilometers per hour or miles per hour. @property (nonatomic) float speed Discussion The possible range is currently limited from -99.9 to 99.9, inclusive. Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a decimal number in the UI. The decimal part will be rounded to one digit. Currently pace and speed cannot be presented at the same time. Setting pace will discard the value set through speed. Declared In PBWatch+Sports.h | | pebblekit\_ios | [heartRate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/heartRate) | | The current heart rate in beats per minute. @property (nonatomic) uint8\_t heartRate Discussion Currently there’s no way to stop sending heart rate values if one heart rate value was sent. The last value will be shown in the UI. If the heart rate has never been set before, this property will return zero. Declared In PBWatch+Sports.h | | pebblekit\_ios | [customLabel](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/customLabel) | | A custom label to show in the sports UI. @property (nonatomic, copy, nullable) NSString \*customLabel Discussion The maximum number of characters is ~10, but this maximum is not enforced. The label will be sent in upper case to the watch. To be sent, both customLabel and customValue have to be set to non-nil values. Declared In PBWatch+Sports.h | | pebblekit\_ios | [customValue](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/customValue) | | A custom value to show in the sports UI. @property (nonatomic, copy, nullable) NSString \*customValue Discussion The maximum number of characters is ~8, but the maximum is not enforced. To be sent, both customValue and customLabel have to be set to non-nil values. Declared In PBWatch+Sports.h | | pebblekit\_ios | [appMessageDictionary](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/appMessageDictionary) | | Creates an update dictionary from the receiver, that can be used with [PBWatch sportsAppUpdate:onSent:]. @property (nonatomic, readonly) NSDictionary \*appMessageDictionary Discussion You should prefer using [PBSportsUpdate updateWatch:completion:]. See Also [PBWatch sportsAppUpdate:onSent:] Declared In PBWatch+Sports.h | | pebblekit\_ios | [updateWatch:completion:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/updateWatch:completion:) | | Sends an update to the watch. - (void)updateWatch:(PBWatch \*)watch completion:(void ( ^ \_\_nullable ) ( NSError \*\_\_nullable error ))completion Parameters watch The watch to send the update to. completion Block that will be called when the update either succeeds or fails. error: nil if the operation was successful, or else an NSError with more information on why it failed. Discussion The method tries to send the minimal set of changes since the last time the method was used, to try to minimize communication with the watch. Declared In PBWatch+Sports.h | | pebblekit\_ios | [timeStringFromSeconds:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/timeStringFromSeconds:) | | Creates a formatted time string from a total seconds value, formatted as “h:mm:ss”. + (NSString \*)timeStringFromSeconds:(NSTimeInterval)seconds Parameters seconds The number of seconds from which to create the time string. Return Value Formatted time as “h:mm:ss” Discussion For example, supplying the value 3930.0f seconds will return @“1:05:30”. Declared In PBWatch+Sports.h | | pebblekit\_ios | [decimalStringFromFloat:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/decimalStringFromFloat:) | | Creates a formatted decimal string with one decimal number. + (NSString \*)decimalStringFromFloat:(float)decimal Parameters decimal The decimal number to format as a string. Return Value The formatted decimal number. Discussion For example, supplying the value 13.42f will return @“13.4”. Declared In PBWatch+Sports.h | | pebblekit\_ios | [data](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/data) | | General purpose data variable. (Deprecated: Prefer either pace or speed.) @property (nonatomic) float data Discussion Note: The original property design is very broken and it has been deprecated in favor of both pace and speed. This value will act as pace from now on. The possible range is currently limited from -3599 to 3599, inclusive (±59min 59sec). Values larger or smaller than the limits will be transformed into the maximum or minimum, respectively. It will be presented as a duration string in the UI. Minutes and seconds will be separated by colons. The decimal part will be discarded for the presentation. Setting a value in either pace or speed will discard the value set through data. See Also [PBWatch sportsAppSetLabel:onSent:] Declared In PBWatch+Sports.h | | pebblekit\_ios | [updateWithTime:distance:data:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/updateWithTime:distance:data:) | | Creates a new PBSportsUpdate with the given time, distance and data. (Deprecated: Prefer -[PBSportsUpdate init] and filling the properties.) + (instancetype)updateWithTime:(NSTimeInterval)time distance:(float)distance data:(float)data Parameters time The current time in seconds. distance The current distance in kilometers or miles. data General purpose data variable. Return Value A new PBSportsUpdate with the given time, distance and data. Declared In PBWatch+Sports.h | | pebblekit\_ios | [dictionary](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/dictionary) | | Creates an update dictionary from the receiver that can be used with [PBWatch sportsAppUpdate:onSent:]. (Deprecated: Prefer using -[PBSportsUpdate appMessageDictionary].) - (NSDictionary \*)dictionary See Also [PBWatch sportsAppUpdate:onSent:] Declared In PBWatch+Sports.h | | pebblekit\_ios | [timeStringFromFloat:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md#//api/name/timeStringFromFloat:) | | Creates a formatted time string from a total seconds value, formatted as “h:mm:ss”. (Deprecated: Prefer +[PBSportsUpdate timeStringFromSeconds:].) + (NSString \*)timeStringFromFloat:(float)seconds Parameters seconds The number of seconds from which to create the time string. Return Value Formatted time as “h:mm:ss” Discussion For example, supplying the value 3930.0f seconds will return @“1:05:30”. Declared In PBWatch+Sports.h | | pebblekit\_ios | [PBVersionInfo](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md) | | | | pebblekit\_ios | [runningFirmwareMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/runningFirmwareMetadata) | | The metadata of the firmware that is running. @property (nonatomic, readonly, strong, nullable) PBFirmwareMetadata \*runningFirmwareMetadata Declared In PBVersionInfo.h | | pebblekit\_ios | [recoveryFirmwareMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/recoveryFirmwareMetadata) | | The metadata of the recovery firmware that is in storage. @property (nonatomic, readonly, strong, nullable) PBFirmwareMetadata \*recoveryFirmwareMetadata Declared In PBVersionInfo.h | | pebblekit\_ios | [systemResources](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/systemResources) | | The metadata of the system resources. @property (nonatomic, readonly, strong) PBResourceMetadata \*systemResources Declared In PBVersionInfo.h | | pebblekit\_ios | [bootloaderVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/bootloaderVersion) | | The version of the bootloader. @property (nonatomic, readonly) UInt32 bootloaderVersion Declared In PBVersionInfo.h | | pebblekit\_ios | [hardwareVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/hardwareVersion) | | The version of the hardware. @property (nonatomic, readonly, strong) NSString \*hardwareVersion Declared In PBVersionInfo.h | | pebblekit\_ios | [serialNumber](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/serialNumber) | | The serial number. @property (nonatomic, readonly, strong) NSString \*serialNumber Declared In PBVersionInfo.h | | pebblekit\_ios | [deviceAddress](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/deviceAddress) | | The current Bluetooth device address. @property (nonatomic, readonly, strong) NSData \*deviceAddress Declared In PBVersionInfo.h | | pebblekit\_ios | [remoteProtocolCapabilitiesFlags](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/remoteProtocolCapabilitiesFlags) | | Flags for the supported protocol features of the watch. @property (nonatomic, readonly, assign) PBRemoteProtocolCapabilitiesFlags remoteProtocolCapabilitiesFlags Declared In PBVersionInfo.h | | pebblekit\_ios | [hasBeenUnfaithful](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/hasBeenUnfaithful) | | Indicates if the previous connection for the watch was to this device. @property (nonatomic, readonly, assign) BOOL hasBeenUnfaithful Discussion If the watch has been connected to this device, but connects to other device before connecting again to this device, the value will be YES. Declared In PBVersionInfo.h | | pebblekit\_ios | [hasRecoveryFirmware](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/hasRecoveryFirmware) | | YES if valid recovery firmware is installed, NO if not. - (BOOL)hasRecoveryFirmware Declared In PBVersionInfo.h | | pebblekit\_ios | [hasSystemResources](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/hasSystemResources) | | YES if valid system resources are installed, NO if not. - (BOOL)hasSystemResources Declared In PBVersionInfo.h | | pebblekit\_ios | [appMessagesSupported](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md#//api/name/appMessagesSupported) | | YES if app messages are supported, NO if not. - (BOOL)appMessagesSupported Declared In PBVersionInfo.h | | pebblekit\_ios | [PBFirmwareVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md) | | | | pebblekit\_ios | [timestamp](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/timestamp) | | The version timestamp. @property (nonatomic, readonly) uint32\_t timestamp Discussion The timestamp is in the final comparison equation, in the -compare: method, after evaluating major, minor, and revision components. Declared In PBFirmwareVersion.h | | pebblekit\_ios | [commitHash](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/commitHash) | | The version’s git commit hash. @property (nonatomic, readonly, copy, nullable) NSString \*commitHash Discussion The commit hash is not used in the -compare: method. Declared In PBFirmwareVersion.h | | pebblekit\_ios | [tag](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/tag) | | The version’s git tag. @property (nonatomic, readonly, copy) NSString \*tag Declared In PBFirmwareVersion.h | | pebblekit\_ios | [firmwareVersionWithString:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/firmwareVersionWithString:) | | Creates a PBFirmwareVersion object given a tag string. + (nullable instancetype)firmwareVersionWithString:(NSString \*)tag Parameters tag The firmware version string to parse. Declared In PBFirmwareVersion.h | | pebblekit\_ios | [firmwareVersionWithTag:commitHash:timestamp:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/firmwareVersionWithTag:commitHash:timestamp:) | | Creates a PBFirmwareVersion object given a tag string and timestamp. + (nullable instancetype)firmwareVersionWithTag:(NSString \*)tag commitHash:(nullable NSString \*)commitHash timestamp:(uint32\_t)timestamp Parameters tag The firmare version string to parse. commitHash A version’s commit hash. Can be nil. timestamp The version timestamp. Declared In PBFirmwareVersion.h | | pebblekit\_ios | [firmwareVersionWithMajor:minor:revision:suffix:commitHash:timestamp:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/firmwareVersionWithMajor:minor:revision:suffix:commitHash:timestamp:) | | Creates a PBFirmwareVersion object given its components. + (instancetype)firmwareVersionWithMajor:(NSInteger)major minor:(NSInteger)minor revision:(NSInteger)revision suffix:(nullable NSString \*)suffix commitHash:(nullable NSString \*)commitHash timestamp:(uint32\_t)timestamp Parameters major The version major number. minor The version minor number. revision The version revision number. suffix The version suffix. Can be nil. commitHash A version’s commit hash. Can be nil. timestamp The version timestamp. Discussion The tag string will be set to the canonical format vMAJOR.MINOR.REVISION(-SUFFIX). Declared In PBFirmwareVersion.h | | pebblekit\_ios | [os](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/os) | | The OS version component. (Deprecated: Use [PBSemanticVersion majorVersion]) @property (nonatomic, readonly) NSInteger os Declared In PBFirmwareVersion+Legacy.h | | pebblekit\_ios | [major](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/major) | | The major version component. (Deprecated: Use [PBSemanticVersion minorVersion]) @property (nonatomic, readonly) NSInteger major Discussion Note: If a version tag string does not contain a major component, it will be set to 0. Declared In PBFirmwareVersion+Legacy.h | | pebblekit\_ios | [minor](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/minor) | | The minor version component. (Deprecated: Use [PBSemanticVersion revisionVersion]) @property (nonatomic, readonly) NSInteger minor Discussion Note: If a version tag string does not contain a major component, it will be set to 0. Declared In PBFirmwareVersion+Legacy.h | | pebblekit\_ios | [firmwareVersionWithOS:major:minor:suffix:commitHash:timestamp:](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md#//api/name/firmwareVersionWithOS:major:minor:suffix:commitHash:timestamp:) | | Creates a PBFirmwareVersion object given its components. (Deprecated: Use +[PBFirmwareVersion firmwareVersionWithMajor:minor:revision:suffix:commitHash:timestamp:]) + (nullable instancetype)firmwareVersionWithOS:(NSInteger)os major:(NSInteger)major minor:(NSInteger)minor suffix:(nullable NSString \*)suffix commitHash:(nullable NSString \*)commitHash timestamp:(uint32\_t)timestamp Parameters os The version major number. major The version minor number. minor The version revision number. suffix The version suffix. Can be nil. commitHash A version’s commit hash. Can be nil. timestamp The version timestamp. Discussion The tag string will be set to the canonical format vMAJOR.MINOR.REVISION(-SUFFIX). Declared In PBFirmwareVersion+Legacy.h | | pebblekit\_ios | [PBDataLoggingType](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBDataLoggingType.md) | | | | pebblekit\_ios | [PBPebbleKitLogFlag](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogFlag.md) | | | | pebblekit\_ios | [FirmwareMetadataPlatform](https://developer.repebble.com/docs/pebblekit-ios/Constants/FirmwareMetadataPlatform.md) | | | | pebblekit\_ios | [PBRemoteProtocolCapabilitiesFlags](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBRemoteProtocolCapabilitiesFlags.md) | | | | pebblekit\_ios | [PBPebbleKitLogLevel](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogLevel.md) | | | | pebblekit\_ios | [PBErrorCode](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBErrorCode.md) | | | | pebblekit\_ios | [GolfAppCommand](https://developer.repebble.com/docs/pebblekit-ios/Constants/GolfAppCommand.md) | | | | pebblekit\_ios | [SportsAppActivityState](https://developer.repebble.com/docs/pebblekit-ios/Constants/SportsAppActivityState.md) | | | | pebblekit\_ios | [PBUnitSystem](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBUnitSystem.md) | | | | pebblekit\_ios | [PBDataLoggingServiceDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md) | | | | pebblekit\_ios | [dataLoggingService:hasByteArrays:numberOfItems:forDataLoggingSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:hasByteArrays:numberOfItems:forDataLoggingSession:) | | Called by the service every time there is data available that has not been consumed yet. - (BOOL)dataLoggingService:(PBDataLoggingService \*)service hasByteArrays:(const uint8\_t \*const)bytes numberOfItems:(uint16\_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. bytes Pointer to the array of bytes. The array contains (numberOfItems \* session.itemSize) bytes. numberOfItems The number of items that the array contains. session The metadata of the session. Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. Declared In PBDataLoggingService.h | | pebblekit\_ios | [dataLoggingService:hasUInt8s:numberOfItems:forDataLoggingSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:hasUInt8s:numberOfItems:forDataLoggingSession:) | | Called by the service every time there is data available that has not been consumed yet. - (BOOL)dataLoggingService:(PBDataLoggingService \*)service hasUInt8s:(const uint8\_t [])data numberOfItems:(uint16\_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. data Pointer to the array of UInt8`s. numberOfItems The number of items that the array contains. session The metadata of the session. Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. Declared In PBDataLoggingService.h | | pebblekit\_ios | [dataLoggingService:hasUInt16s:numberOfItems:forDataLoggingSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:hasUInt16s:numberOfItems:forDataLoggingSession:) | | Called by the service every time there is data available that has not been consumed yet. - (BOOL)dataLoggingService:(PBDataLoggingService \*)service hasUInt16s:(const uint16\_t [])data numberOfItems:(uint16\_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. data Pointer to the array of UInt16`s. numberOfItems The number of items that the array contains. session The metadata of the session. Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. Declared In PBDataLoggingService.h | | pebblekit\_ios | [dataLoggingService:hasUInt32s:numberOfItems:forDataLoggingSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:hasUInt32s:numberOfItems:forDataLoggingSession:) | | Called by the service every time there is data available that has not been consumed yet. - (BOOL)dataLoggingService:(PBDataLoggingService \*)service hasUInt32s:(const uint32\_t [])data numberOfItems:(uint16\_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. data Pointer to the array of UInt32`s. numberOfItems The number of items that the array contains. session The metadata of the session. Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. Declared In PBDataLoggingService.h | | pebblekit\_ios | [dataLoggingService:hasSInt8s:numberOfItems:forDataLoggingSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:hasSInt8s:numberOfItems:forDataLoggingSession:) | | Called by the service every time there is data available that has not been consumed yet. - (BOOL)dataLoggingService:(PBDataLoggingService \*)service hasSInt8s:(const int8\_t [])data numberOfItems:(uint16\_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. data Pointer to the array of SInt8`s. numberOfItems The number of items that the array contains. session The metadata of the session. Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. Declared In PBDataLoggingService.h | | pebblekit\_ios | [dataLoggingService:hasSInt16s:numberOfItems:forDataLoggingSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:hasSInt16s:numberOfItems:forDataLoggingSession:) | | Called by the service every time there is data available that has not been consumed yet. - (BOOL)dataLoggingService:(PBDataLoggingService \*)service hasSInt16s:(const int16\_t [])data numberOfItems:(uint16\_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. data Pointer to the array of SInt16`s. numberOfItems The number of items that the array contains. session The metadata of the session. Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. Declared In PBDataLoggingService.h | | pebblekit\_ios | [dataLoggingService:hasSInt32s:numberOfItems:forDataLoggingSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:hasSInt32s:numberOfItems:forDataLoggingSession:) | | Called by the service every time there is data available that has not been consumed yet. - (BOOL)dataLoggingService:(PBDataLoggingService \*)service hasSInt32s:(const int32\_t [])data numberOfItems:(uint16\_t)numberOfItems forDataLoggingSession:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. data Pointer to the array of SInt32`s. numberOfItems The number of items that the array contains. session The metadata of the session. Return Value YES if the data was consumed and the service can discard the data. Return NO if the data was not be consumed after the method returned. If NO is returned, the next time this callback is invoked for the session, the data argument will (also) contain the data of the items of the previous invocation of the callback. Declared In PBDataLoggingService.h | | pebblekit\_ios | [dataLoggingService:sessionDidFinish:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md#//api/name/dataLoggingService:sessionDidFinish:) | | Called by the service every time a session is finished. This is guaranteed to be called only after all data has been received and consumed by the application. - (void)dataLoggingService:(PBDataLoggingService \*)service sessionDidFinish:(PBDataLoggingSessionMetadata \*)session Parameters service The data logging service. session The metadata of the session. Declared In PBDataLoggingService.h | | pebblekit\_ios | [PBPebbleCentralDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate.md) | | | | pebblekit\_ios | [pebbleCentral:watchDidConnect:isNew:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate.md#//api/name/pebbleCentral:watchDidConnect:isNew:) | | The Pebble Central responsible for calling the delegate method. - (void)pebbleCentral:(PBPebbleCentral \*)central watchDidConnect:(PBWatch \*)watch isNew:(BOOL)isNew Parameters central The Pebble Central responsible for calling the delegate method. watch The PBWatch object representing the watch that was connected. isNew YES if the watch has been connected for the first time since the app has been installed or NO if not. Declared In PBPebbleCentral.h | | pebblekit\_ios | [pebbleCentral:watchDidDisconnect:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate.md#//api/name/pebbleCentral:watchDidDisconnect:) | | The Pebble Central responsible for calling the delegate method. - (void)pebbleCentral:(PBPebbleCentral \*)central watchDidDisconnect:(PBWatch \*)watch Parameters central The Pebble Central responsible for calling the delegate method. watch The PBWatch object representing the watch that was disconnected. Declared In PBPebbleCentral.h | | pebblekit\_ios | [PBWatchDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md) | | | | pebblekit\_ios | [watchDidDisconnect:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md#//api/name/watchDidDisconnect:) | | Called when the watch got disconnected. - (void)watchDidDisconnect:(PBWatch \*)watch Parameters watch The watch that was disconnected. Declared In PBWatch.h | | pebblekit\_ios | [watch:handleError:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md#//api/name/watch:handleError:) | | Called when the watch caught an error. - (void)watch:(PBWatch \*)watch handleError:(NSError \*)error Parameters watch The watch that caught the error. error The error catch by the watch. Declared In PBWatch.h | | pebblekit\_ios | [watchWillResetSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md#//api/name/watchWillResetSession:) | | Called when an internal session is about to be reset. - (void)watchWillResetSession:(PBWatch \*)watch Parameters watch The watch that will reset its session. Declared In PBWatch.h | | pebblekit\_ios | [watchDidOpenSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md#//api/name/watchDidOpenSession:) | | Called when an internal session is opened - (void)watchDidOpenSession:(PBWatch \*)watch Parameters watch The watch that opened a session. Declared In PBWatch.h | | pebblekit\_ios | [watchDidCloseSession:](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md#//api/name/watchDidCloseSession:) | | Called when an internal EASession is closed - (void)watchDidCloseSession:(PBWatch \*)watch Parameters watch The watch that closed its session. Declared In PBWatch.h | | pebblekit\_ios | [hierarchy](https://developer.repebble.com/docs/pebblekit-ios/hierarchy.md) | | | | pebblekit\_ios | [index](https://developer.repebble.com/docs/pebblekit-ios/index.md) | | | --- # tree Source: https://developer.repebble.com/docs/tree.html - c - [Foundation](https://developer.repebble.com/docs/c/Foundation.md) - [Alloy](https://developer.repebble.com/docs/c/Foundation/Alloy.md) - [App](https://developer.repebble.com/docs/c/Foundation/App.md) - [App Communication](https://developer.repebble.com/docs/c/Foundation/App_Communication.md) - [App Glance](https://developer.repebble.com/docs/c/Foundation/App_Glance.md) - [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md) - [AppSync](https://developer.repebble.com/docs/c/Foundation/AppSync.md) - [AppWorker](https://developer.repebble.com/docs/c/Foundation/AppWorker.md) - [DataLogging](https://developer.repebble.com/docs/c/Foundation/DataLogging.md) - [DataStructures](https://developer.repebble.com/docs/c/Foundation/DataStructures/) - [UUID](https://developer.repebble.com/docs/c/Foundation/DataStructures/UUID.md) - [Dictation](https://developer.repebble.com/docs/c/Foundation/Dictation.md) - [Dictionary](https://developer.repebble.com/docs/c/Foundation/Dictionary.md) - [Event Service](https://developer.repebble.com/docs/c/Foundation/Event_Service.md) - [AccelerometerService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AccelerometerService.md) - [AlarmService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AlarmService.md) - [AppFocusService](https://developer.repebble.com/docs/c/Foundation/Event_Service/AppFocusService.md) - [BacklightService](https://developer.repebble.com/docs/c/Foundation/Event_Service/BacklightService.md) - [BatteryStateService](https://developer.repebble.com/docs/c/Foundation/Event_Service/BatteryStateService.md) - [CompassService](https://developer.repebble.com/docs/c/Foundation/Event_Service/CompassService.md) - [ConnectionService](https://developer.repebble.com/docs/c/Foundation/Event_Service/ConnectionService.md) - [HealthService](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md) - [TickTimerService](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md) - [TouchService](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md) - [Exit Reason](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md) - [Internationalization](https://developer.repebble.com/docs/c/Foundation/Internationalization.md) - [Launch Reason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md) - [Logging](https://developer.repebble.com/docs/c/Foundation/Logging.md) - [Math](https://developer.repebble.com/docs/c/Foundation/Math.md) - [Memory Management](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md) - [Platform](https://developer.repebble.com/docs/c/Foundation/Platform.md) - [Resources](https://developer.repebble.com/docs/c/Foundation/Resources.md) - [File Formats](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats.md) - [PBI File Format](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PBI_File_Format.md) - [PNG8 File Format](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PNG8_File_Format.md) - [Storage](https://developer.repebble.com/docs/c/Foundation/Storage.md) - [Timer](https://developer.repebble.com/docs/c/Foundation/Timer.md) - [Wakeup](https://developer.repebble.com/docs/c/Foundation/Wakeup.md) - [Wall Time](https://developer.repebble.com/docs/c/Foundation/Wall_Time.md) - [WatchInfo](https://developer.repebble.com/docs/c/Foundation/WatchInfo.md) - [Graphics](https://developer.repebble.com/docs/c/Graphics.md) - [Draw Commands](https://developer.repebble.com/docs/c/Graphics/Draw_Commands.md) - [Drawing Paths](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md) - [Drawing Primitives](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md) - [Drawing Text](https://developer.repebble.com/docs/c/Graphics/Drawing_Text.md) - [Fonts](https://developer.repebble.com/docs/c/Graphics/Fonts.md) - [Graphics Context](https://developer.repebble.com/docs/c/Graphics/Graphics_Context.md) - [Graphics Types](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md) - [Color Definitions](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md) - [User Interface](https://developer.repebble.com/docs/c/User_Interface.md) - [Animation](https://developer.repebble.com/docs/c/User_Interface/Animation.md) - [PropertyAnimation](https://developer.repebble.com/docs/c/User_Interface/Animation/PropertyAnimation.md) - [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md) - [Gesture Recognizers](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md) - [Layers](https://developer.repebble.com/docs/c/User_Interface/Layers.md) - [ActionBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md) - [BitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md) - [MenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md) - [RotBitmapLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/RotBitmapLayer.md) - [ScrollLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md) - [SimpleMenuLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/SimpleMenuLayer.md) - [StatusBarLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/StatusBarLayer.md) - [TextLayer](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md) - [Light](https://developer.repebble.com/docs/c/User_Interface/Light.md) - [Preferences](https://developer.repebble.com/docs/c/User_Interface/Preferences.md) - [Speaker](https://developer.repebble.com/docs/c/User_Interface/Speaker.md) - [UnobstructedArea](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md) - [Vibes](https://developer.repebble.com/docs/c/User_Interface/Vibes.md) - [Window](https://developer.repebble.com/docs/c/User_Interface/Window.md) - [ActionMenu](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md) - [NumberWindow](https://developer.repebble.com/docs/c/User_Interface/Window/NumberWindow.md) - [Window Stack](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md) - [Worker](https://developer.repebble.com/docs/c/Worker.md) - [Standard C](https://developer.repebble.com/docs/c/Standard_C.md) - [Format](https://developer.repebble.com/docs/c/Standard_C/Format.md) - [Locale](https://developer.repebble.com/docs/c/Standard_C/Locale.md) - [Math](https://developer.repebble.com/docs/c/Standard_C/Math.md) - [Memory](https://developer.repebble.com/docs/c/Standard_C/Memory.md) - [String](https://developer.repebble.com/docs/c/Standard_C/String.md) - [Time](https://developer.repebble.com/docs/c/Standard_C/Time.md) - pebblekit\_js - [Pebble](https://developer.repebble.com/docs/pebblekit-js/Pebble.md) - [addEventListener](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#addEventListener) - [removeEventListener](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#removeEventListener) - [showSimpleNotificationOnPebble](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#showSimpleNotificationOnPebble) - [sendAppMessage](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#sendAppMessage) - [getTimelineToken](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getTimelineToken) - [timelineSubscribe](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#timelineSubscribe) - [timelineUnsubscribe](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#timelineUnsubscribe) - [timelineSubscriptions](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#timelineSubscriptions) - [insertTimelinePin](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#insertTimelinePin) - [deleteTimelinePin](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#deleteTimelinePin) - [getActiveWatchInfo](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getActiveWatchInfo) - [getAccountToken](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getAccountToken) - [getWatchToken](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#getWatchToken) - [appGlanceReload](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#appGlanceReload) - [TimelineTopicsCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#TimelineTopicsCallback) - [openURL](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#openURL) - [enumeratePlugins](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#enumeratePlugins) - [subscribeToSource](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#subscribeToSource) - [invokeAction](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#invokeAction) - [sendConfigMessage](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#sendConfigMessage) - [AppGlanceReloadFailureCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppGlanceReloadFailureCallback) - [AppMessageAckCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppMessageAckCallback) - [AppMessageNackCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppMessageNackCallback) - [EventCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#EventCallback) - [TimelineTokenCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#TimelineTokenCallback) - [AppGlanceReloadSuccessCallback](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppGlanceReloadSuccessCallback) - [WatchInfo](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#WatchInfo) - [AppGlanceSlice](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#AppGlanceSlice) - pebblekit\_android - [com.getpebble.android.kit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit.md) - [Constants](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.md) - [PebbleKit](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.md) - [PebbleKit.FirmwareVersionInfo](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.FirmwareVersionInfo.md) - [PebbleKit.PebbleAckReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleAckReceiver.md) - [PebbleKit.PebbleDataLogReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataLogReceiver.md) - [PebbleKit.PebbleDataReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleDataReceiver.md) - [PebbleKit.PebbleNackReceiver](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit.PebbleNackReceiver.md) - [Constants.PebbleAppType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleAppType.md) - [Constants.PebbleDataType](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/Constants.PebbleDataType.md) - [com.getpebble.android.kit.util](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util.md) - [PebbleDictionary](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.md) - [PebbleTuple](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.md) - [SportsState](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/SportsState.md) - [PebbleDictionary.PebbleDictTypeException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.PebbleDictTypeException.md) - [PebbleDictionary.TupleOverflowException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary.TupleOverflowException.md) - [PebbleTuple.ValueOverflowException](https://developer.repebble.com/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleTuple.ValueOverflowException.md) - pebblekit\_ios - [Class References](https://developer.repebble.com/docs/pebblekit-ios.md#class-references) - [PBBitmap](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBBitmap.md) - [PBDataLoggingService](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingService.md) - [PBDataLoggingSessionMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBDataLoggingSessionMetadata.md) - [PBFirmwareMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareMetadata.md) - [PBFirmwareVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBFirmwareVersion.md) - [PBPebbleCentral](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md) - [PBPebbleKitLogging](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleKitLogging.md) - [PBResourceMetadata](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBResourceMetadata.md) - [PBSemanticVersion](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSemanticVersion.md) - [PBSportsUpdate](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBSportsUpdate.md) - [PBVersionInfo](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBVersionInfo.md) - [PBWatch](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBWatch.md) - [Protocol References](https://developer.repebble.com/docs/pebblekit-ios.md#protocol-references) - [PBDataLoggingServiceDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate.md) - [PBPebbleCentralDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBPebbleCentralDelegate.md) - [PBWatchDelegate](https://developer.repebble.com/docs/pebblekit-ios/Protocols/PBWatchDelegate.md) - [Constant References](https://developer.repebble.com/docs/pebblekit-ios.md#constant-references) - [FirmwareMetadataPlatform](https://developer.repebble.com/docs/pebblekit-ios/Constants/FirmwareMetadataPlatform.md) - [GolfAppCommand](https://developer.repebble.com/docs/pebblekit-ios/Constants/GolfAppCommand.md) - [PBDataLoggingType](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBDataLoggingType.md) - [PBErrorCode](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBErrorCode.md) - [PBPebbleKitLogFlag](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogFlag.md) - [PBPebbleKitLogLevel](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBPebbleKitLogLevel.md) - [PBRemoteProtocolCapabilitiesFlags](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBRemoteProtocolCapabilitiesFlags.md) - [PBUnitSystem](https://developer.repebble.com/docs/pebblekit-ios/Constants/PBUnitSystem.md) - [SportsAppActivityState](https://developer.repebble.com/docs/pebblekit-ios/Constants/SportsAppActivityState.md) - [Category References](https://developer.repebble.com/docs/pebblekit-ios.md#category-references) - [NSData(Pebble)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSData%2BPebble/) - [NSDictionary(Pebble)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSDictionary%2BPebble/) - [NSError(Pebble)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSError%2BPebble/) - [NSNumber(PBStandardIntegerExtensions)](https://developer.repebble.com/docs/pebblekit-ios/Categories/NSNumber%2BPBStandardIntegerExtensions/) --- # Contributing to PebbleOS Source: https://developer.repebble.com/pebbleos/contributing/ PebbleOS is developed at [github.com/coredevices/pebbleos](https://github.com/coredevices/pebbleos). Changes arrive as pull requests, bugs are tracked in the repository's issues, and discussion happens in the repository's Discussions and on the [forum](https://forum.repebble.com). This page points to the imported documentation for each step and answers the questions that come up most often. ## Setting Up a Build - Follow [Prerequisites](https://developer.repebble.com/pebbleos/docs/development/getting_started.md) to install the PebbleOS SDK toolchain bundle, the system packages and the Python environment, and to clone the repository with its submodules. - [The `pbl` CLI](https://developer.repebble.com/pebbleos/docs/development/pbl.md) is the command you drive the build with: `pbl configure --board `, then `pbl build`. [Configuration Options](https://developer.repebble.com/pebbleos/docs/development/options.md) lists the `-D` options `pbl configure` accepts. - [QEMU](https://developer.repebble.com/pebbleos/docs/development/qemu.md) runs a build without hardware. Configure one of the `qemu_flint`, `qemu_emery` or `qemu_gabbro` boards and run `pbl qemu`. The `pebble` tool installs `.pbw` files into the running emulator with `pebble install --qemu`. - [Building firmware](https://developer.repebble.com/pebbleos/docs/development/building_fw.md) covers flashing a board with `pbl flash` and bundling a `.pbz` for a sealed watch. - [Debugging](https://developer.repebble.com/pebbleos/docs/development/debugging.md) covers `pbl debug`, the `pbl` GDB commands and reading logs. ## Sending a Change The [Contribution Guidelines](https://developer.repebble.com/pebbleos/docs/development/contributing.md) are the rules the repository enforces. In short: - Every commit carries a Developer Certificate of Origin sign-off. Commit with `git commit -s` so git adds the `Signed-off-by:` line, using the same name and email as the commit author. - Each commit is one logical change, with a message in the form `area: description`, for example `applib: add touch service`. - C code is formatted with clang-format and Python with ruff. - A change that adds a function apps can call also needs the steps on [Exposing functions to the SDK](https://developer.repebble.com/pebbleos/docs/development/sdk_export.md). Changes to the firmware documentation go in the `docs/` directory of the pebbleos repository, not on this site. The imported pages are regenerated from there. ## Installing a Custom Build on a Watch A sealed watch takes firmware over Bluetooth as a `.pbz` bundle. After `pbl build`, run `pbl bundle`; the bundle is written to `build/`. Copy it to the phone, then in the Pebble mobile app: - Open _Settings_ and turn on _Show debug options_. - Open the _Devices_ tab, tap your watch, then _Firmware Update Debug_. - Tap _Sideload FW_ and choose the `.pbz` file. The `pebble` tool can also install a bundle onto a connected watch: ``` $ pebble fw install build/.pbz ``` [Building firmware](https://developer.repebble.com/pebbleos/docs/development/building_fw.md) has the current steps, including a scripted route over `adb` on Android. A board with its debug connector exposed is flashed directly with `pbl flash` instead. ## Recovery Firmware The watch keeps a second, minimal firmware image called PRF (Pebble Recovery Firmware). The bootloader falls back to it when the main firmware cannot boot, and the Pebble mobile app reinstalls the main firmware from it. PRF is built from the same source as a separate variant, `pbl configure --variant prf`; the [Architecture overview](https://developer.repebble.com/pebbleos/docs/architecture.md) describes how the two images relate. To reboot a running watch into PRF, hold _Back_, _Up_ and _Select_ together, or run: ``` $ pebble fw enter-prf ``` `pebble fw install` takes a `--slot` option for bundles that contain more than one firmware image. ## Emulator Support for Pebble Time 2 and Pebble Round 2 There is no QEMU model of the SiFli SF32LB52 SoC used in Pebble Time 2 (`obelix`) and Pebble Round 2 (`getafix`). The `qemu_emery` and `qemu_gabbro` boards run the emery and gabbro platform code on the repository's QEMU SoC target (`soc/qemu`, a Cortex-M33 machine), so the UI, app behaviour and the Pebble protocol can be tested without the real hardware. The SoC drivers of the physical watches are not exercised by the emulator. ## The Firmware Development Kit The imported pages mention a firmware development kit. It is a watch mainboard with its debug connector exposed together with a programming board, which lets `pbl flash`, `pbl console` and `pbl debug` talk to the board over SWD and a serial port. For Pebble 2 Duo this is the Core B2B v2 board described on the [Asterix board page](https://developer.repebble.com/pebbleos/docs/boards/asterix.md). Without one, a custom build is installed as a `.pbz` bundle as described above. --- # PebbleOS Source: https://developer.repebble.com/pebbleos/ PebbleOS is the firmware that runs on Pebble watches. Its source is developed in the open at [github.com/coredevices/pebbleos](https://github.com/coredevices/pebbleos) under the Apache License 2.0. This section of the site is for developers who want to build, run or change the firmware itself. To write watchapps and watchfaces, start with [Installing the Pebble SDK](https://developer.repebble.com/sdk.md) and the [Guides](https://developer.repebble.com/guides.md) instead. The pages under [PebbleOS documentation](#where-the-documentation-starts) are generated from the `docs/` directory of the pebbleos repository on every build of this site, from the `main` branch. The same content is published at [pebbleos-core.readthedocs.io](https://pebbleos-core.readthedocs.io). Each imported page ends with the PebbleOS commit it was generated from and a link to its source file. ## Start Here The steps below take a build from nothing to running in the emulator and installed on a watch. Each step links to the imported page that covers it, so the instructions themselves come from the pebbleos repository. 1. Set up the build environment. [Prerequisites](https://developer.repebble.com/pebbleos/docs/development/getting_started.md#prerequisites) installs the [PebbleOS SDK](https://developer.repebble.com/pebbleos/docs/development/getting_started.md#pebbleos-sdk) toolchain bundle and the [system packages](https://developer.repebble.com/pebbleos/docs/development/getting_started.md#system-level-dependencies), then [gets the source](https://developer.repebble.com/pebbleos/docs/development/getting_started.md#get-the-source-code) with its submodules and installs the [Python dependencies](https://developer.repebble.com/pebbleos/docs/development/getting_started.md#python-dependencies). 2. Configure and build for an emulator board. [Build](https://developer.repebble.com/pebbleos/docs/development/qemu.md#build) on the QEMU page runs `pbl configure --board qemu_flint` and `pbl build`. [Choosing your target](https://developer.repebble.com/pebbleos/docs/development/options.md#choosing-your-target) lists the boards and revisions `pbl configure` accepts. 3. Run the build. [Run](https://developer.repebble.com/pebbleos/docs/development/qemu.md#run) starts the emulator with `pbl qemu`, and [Install PBW applications](https://developer.repebble.com/pebbleos/docs/development/qemu.md#install-pbw-applications) installs an app into it with the `pebble` tool. 4. Configure and build again for the board of your watch, from the table under [Boards](#boards), bundle a `.pbz` with `pbl bundle`, and install it in one of three ways: sideload the file from the Pebble mobile app (_Firmware Update Debug_ \> _Sideload FW_, with debug options shown), as described in [Loading firmware via Bluetooth](https://developer.repebble.com/pebbleos/docs/development/building_fw.md#loading-firmware-via-bluetooth); send it through the [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) with `pebble fw install .pbz --phone `; or, on a board with its debug connector exposed, flash it with `pbl flash` as described in [Loading firmware with a firmware development kit](https://developer.repebble.com/pebbleos/docs/development/building_fw.md#loading-firmware-with-a-firmware-development-kit). Read [Installing a Custom Build](#installing-a-custom-build) before step 4. ## Installing a Custom Build **Important** A firmware change can modify or overwrite the bootloader or the recovery firmware (PRF). If either stops working, the watch can no longer take an update over Bluetooth and can only be reprogrammed through its debug connector. Keep changes away from the bootloader and PRF, and test in the emulator before flashing a watch. When a firmware fails to start, the bootloader records the attempt in the boot bits and after repeated failures boots PRF instead. From PRF the Pebble mobile app installs a firmware again. To get to PRF by hand, hold _Back_, _Up_ and _Select_ together for five seconds, or run `pebble fw enter-prf`. [Recovery Firmware](https://developer.repebble.com/pebbleos/contributing.md#recovery-firmware) has the details. If the bootloader or PRF has been erased, the watch can only be programmed through its debug connector: over SWD with openocd on Pebble 2 Duo, and over the serial adapter with sftool, which does not depend on the bootloader, on Pebble Time 2 and Pebble Round 2. A sealed watch has no accessible debug connector; a [firmware development kit](https://developer.repebble.com/pebbleos/contributing.md#the-firmware-development-kit) does. ## Boards The firmware is configured for one board at a time with `pbl configure --board `. The repository at `main` defines these boards: | Board | Watch | Platform | SoC | | --- | --- | --- | --- | | `asterix` | Pebble 2 Duo | flint | Nordic nRF52840 | | `obelix` | Pebble Time 2 | emery | SiFli SF32LB52 | | `getafix` | Pebble Round 2 | gabbro | SiFli SF32LB52 | | `qemu_flint` | Emulator | flint | QEMU, Cortex-M4 | | `qemu_emery` | Emulator | emery | QEMU, Cortex-M33 | | `qemu_gabbro` | Emulator | gabbro | QEMU, Cortex-M33 | The platform column is the name the SDK uses for the same hardware, for example in `targetPlatforms` in `package.json`. The [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) guide lists what each platform has, such as display size and sensors. Hardware revisions are selected with an `@` suffix, for example `pbl configure --board obelix@pvt`. The revisions each board supports are listed in `boards//.yml`. The repository has no board definitions for the watches made by Pebble Technology Corporation (the aplite, basalt, chalk and diorite platforms). > Note: The imported documentation has a board page for Asterix only. The `obelix`, `getafix` and QEMU boards are documented by their `Kconfig` and `defconfig` files in the repository. ## Translating PebbleOS Translations of the firmware's menus, settings and system messages are made at [translate.repebble.com](https://translate.repebble.com). The site runs [Weblate](https://weblate.org) with a Pebble extension that previews each string with the PebbleOS text renderer, checks that the watch fonts cover the characters a language needs, and builds draft language packs so a translation can be tried on a watch before it is reviewed. To translate: - Sign in at [translate.repebble.com](https://translate.repebble.com) with your Pebble account. - Pick the language to work on. If it is not listed, start it with the new-language wizard, which checks font coverage and lets you add a font if the built-in fonts do not cover the script. - Translate strings. Each language has community reviewers who check new translations before they are included in a language pack. Language packs are built from the site. The `pblpack` command line tool that built language packs from `.po` files is deprecated and no longer the way to produce them. See the [launch post](https://developer.repebble.com/blog/2026/10/07/Take-PebbleOS-To-The-World.md) for the background. ## How the Firmware, the SDK and the Mobile App Relate Watchapps do not link against the firmware. The firmware build generates the app SDK from its own sources: `pebble.h` with the exported declarations, and `libpebble.a` with trampolines that call into the firmware through a function table compiled into the firmware image. Apps run as unprivileged processes and reach OS state through syscalls. The imported [Exposing functions to the SDK](https://developer.repebble.com/pebbleos/docs/development/sdk_export.md) page describes how a firmware function becomes an SDK API. The SDK that developers install with `pebble sdk install` is built from a firmware release. The C API reference on this site at [/docs/c/](https://developer.repebble.com/docs/c.md) is generated from the pebbleos release tag recorded in the site's `EMERY_SDK_VERSION` file. The firmware documentation in this section tracks `main`, so it can describe functions that are not yet in a released SDK. The Pebble mobile app pairs with the watch over Bluetooth, installs apps and firmware updates, and runs the PebbleKit JS side of apps. During development it also carries the [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) that the `pebble` tool uses to install and debug apps on a watch. Its source is at [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp). Changes to the firmware are often paired with changes to the Pebble mobile app, for example a new Pebble Protocol endpoint or a change to how firmware is installed. The mobile app can connect to a PebbleOS emulator over TCP in place of a watch, so both sides of a change can be tested without hardware. [_Testing with the Pebble Mobile App_](https://developer.repebble.com/guides/tools-and-resources/testing-with-the-mobile-app.md) covers running the phone app against the emulator. The [Mobile App](https://developer.repebble.com/mobile-app.md) section covers building the app and sending changes to its repository. > Note: Two different things are called an SDK. The Pebble SDK builds watchapps. The PebbleOS SDK at [github.com/coredevices/PebbleOS-SDK](https://github.com/coredevices/PebbleOS-SDK) is the toolchain bundle for building the firmware: the ARM GNU toolchain, Pebble QEMU and flashing tools. The imported pages mean the second one. ## Where the Documentation Starts - [Prerequisites](https://developer.repebble.com/pebbleos/docs/development/getting_started.md) sets up a build environment and gets the source. The rest of the Development pages cover the `pbl` tool, configuration options, building and flashing, testing, the emulator, debugging and contributing. - [Architecture overview](https://developer.repebble.com/pebbleos/docs/architecture.md) is a tour of the source tree with links to the design documents. - [Boards](https://developer.repebble.com/pebbleos/docs/boards.md) describes the hardware and how to connect a programmer. - [Reference](https://developer.repebble.com/pebbleos/docs/reference.md) holds protocol specifications, file formats and external resources. - The [Firmware API Reference](https://developer.repebble.com/pebbleos/apidoc/index.html) is the Doxygen output for the firmware's public headers under `include/`: kernel, drivers, services and subsystems. It describes interfaces inside the firmware; the [C API reference](https://developer.repebble.com/docs/c.md) is the one to use when writing apps. [Contributing to PebbleOS](https://developer.repebble.com/pebbleos/contributing.md) collects the steps for getting a change merged and answers to the questions that come up most often on the forum. --- # Asterix (Pebble 2 Duo) Source: https://developer.repebble.com/pebbleos/docs/boards/asterix/ ## Programming The Asterix/Pebble 2 Duo (announced as Core 2 Duo) mainboard has a B2B (Board-To-Board) connector that gives access to: - MCU VDD, VUSB and GND - MCU SWCLK, SWDIO and RESET - Debug UART RX/TX The connector part number is Molex 5050040812, and the pinout is as shown below. ![../../_images/b2b-pinout.webp](https://developer.repebble.com/pebbleos/docs/images/b2b-pinout.webp) _Asterix B2B connector pinout_ The “Core B2B v2” board has been designed as a companion programming board. It is based on the [Raspberry Pi Debug Probe](https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html). Software for the `Core B2B v2` board is available in the [Core B2B v2 repository](https://github.com/coredevices/asterix_b2b2_fw). Below you can find a picture of how it is connected, and a list of its main features. ![../../_images/asterix-programming.webp](https://developer.repebble.com/pebbleos/docs/images/asterix-programming.webp) _Asterix and Core B2B v2_ Warning External connectors (SWD/UART) expose 1.8V signals. Do not use any adapter that does not operate at 1.8V, or there is a **risk of damage**! 1. Asterix B2B connector 2. USB connector 3. Debug UART routing 4. SWD routing 5. Debug UART pins 6. External SWD connector 7. MCU Reset 8. VUSB switch ### Usage and tips After receiving your Firmware Dev Kit, please update the firmware on the board to the [latest release](https://github.com/coredevices/asterix_b2b2_fw/releases). When using the built-in programmer and USB-UART converter, make sure to place switches (3) and (4) to the right, as shown in the picture below. It is also recommended to provide VUSB while programming so that the watch is always programmable regardless of the battery level. For this to happen, place switch (8) to the left. ![../../_images/b2bv2-switch-orientation.webp](https://developer.repebble.com/pebbleos/docs/images/b2bv2-switch-orientation.webp) _Correct orientation of the switches when using built-in programmer, USB-UART converter and powering VUSB_ - Please make sure to connect the B2Bv2 board in the **EXACT** way detailed in this document. If you connect it in any other orientation, your watch will be rendered unusable. You have been warned. - If you are having trouble programming your watch, check the orientation of the switches. Sometimes they accidentally get switched to the wrong side during shipping. - If your serial console is showing text but not accepting input, then your connector is likely not seated correctly. Press to click it in more. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/boards/asterix/index.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/boards/asterix/index.md). --- # Boards Source: https://developer.repebble.com/pebbleos/docs/boards/ - [Asterix (Pebble 2 Duo)](https://developer.repebble.com/pebbleos/docs/boards/asterix.md) - [Programming](https://developer.repebble.com/pebbleos/docs/boards/asterix.md#programming) - [Usage and tips](https://developer.repebble.com/pebbleos/docs/boards/asterix.md#usage-and-tips) Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/boards/index.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/boards/index.md). --- # Bitmap resources (PBI) Source: https://developer.repebble.com/pebbleos/docs/reference/formats/pbi/ PBI is the raw bitmap format used for image resources and `GBitmap` data. The authoritative description is the Doxygen block in `fw/applib/graphics/gbitmap_pbi.h`; files are produced by `tools/bitmapgen.py` (shipped in the app SDK) and loaded by `gbitmap_init_with_data()` in `fw/applib/graphics/gbitmap.c`. The `info_flags` bit layout is `BitmapInfo` in `fw/applib/graphics/gtypes.h`. A PBI file is a 12-byte header, then pixel data, then (for palettized formats) the palette. All multi-byte fields are little-endian: | Offset | Field | Type | Notes | | --- | --- | --- | --- | | 0 | `row_size_bytes` | `uint16_t` | row stride | | 2 | `info_flags` | `uint16_t` | see below | | 4 | origin | 4 bytes | legacy; ignored by the firmware | | 8 | `width` | `uint16_t` | | | 10 | `height` | `uint16_t` | | | 12 | pixel data | | `row_size_bytes × height` bytes | | … | palette | | ARGB8 entries; palettized formats only | `info_flags`: - bit 0: heap-allocated flag; always 0 in files - bits 1–3: pixel format (below) - bit 4: palette-heap-allocated flag; always 0 in files - bits 5–11: reserved - bits 12–15: file version, currently 1 (version 0 files are 2.x-era 1-bit bitmaps without a format field) ## Pixel formats | Value | Format | Bits/pixel | Row stride | | --- | --- | --- | --- | | 0 | `GBitmapFormat1Bit` | 1 | word-aligned: `((width + 31) / 32) × 4` | | 1 | `GBitmapFormat8Bit` | 8 | `width` | | 2 | `GBitmapFormat1BitPalette` | 1 | byte-aligned: `ceil(width / 8)` | | 3 | `GBitmapFormat2BitPalette` | 2 | byte-aligned: `ceil(width / 4)` | | 4 | `GBitmapFormat4BitPalette` | 4 | byte-aligned: `ceil(width / 2)` | (`GBitmapFormat8BitCircular` exists in the firmware for the round framebuffer but never appears in PBI files.) - **1Bit** : 1 = white, 0 = black; pixels packed LSB-first within each 32-bit word; padding bits are ignored. - **8Bit** : one `GColor8` (ARGB8, 2 bits per channel) byte per pixel. - **Palettized** : pixels are palette indices, packed MSB-first within each byte. The palette follows the pixel data and holds exactly 2^bpp ARGB8 entries (2, 4 or 16); the SDK tooling reduces images to the smallest bit depth that covers the colors used. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/formats/pbi.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/formats/pbi.md). --- # BlobDB endpoint Source: https://developer.repebble.com/pebbleos/docs/reference/blob-db/ BlobDB is the key-value store the phone uses to push data to the watch (notifications, timeline pins, app metadata, weather, settings, …). Two Pebble Protocol endpoints speak it: `0xb1db` for phone-initiated writes (`fw/services/blob_db/endpoint.c`) and `0xb2db` for watch-initiated sync (`fw/services/blob_db/endpoint2.c`). Command opcodes and response codes are in `include/pbl/services/blob_db/endpoint_private.h`, database ids in `include/pbl/services/blob_db/api.h`. All multi-byte fields are little-endian. ## Commands (endpoint `0xb1db`) Every message starts with a `uint8_t` command and a `uint16_t` token the watch echoes in its response. Keys and values are length-prefixed: `uint8_t key_size` + key bytes, `uint16_t value_size` + value bytes. | Command | Opcode | Body after token | | --- | --- | --- | | INSERT | `0x01` | database id (`uint8_t`), key, value | | DELETE | `0x04` | database id, key | | CLEAR | `0x05` | database id — drops all entries | | INSERT\_WITH\_TIMESTAMP | `0x0D` | database id, `uint32_t` timestamp, key, value | `0x02` (READ) and `0x03` (UPDATE) are defined but not implemented. INSERT\_WITH\_TIMESTAMP is for conflict resolution: if the watch’s copy is newer, it answers DATA\_STALE and the phone should re-sync. The timestamp is currently only honored for the Settings database; other databases treat it as a plain INSERT. The watch responds to every command with `uint16_t token` + `uint8_t response code`: | Code | Meaning | | --- | --- | | `0x01` | SUCCESS | | `0x02` | GENERAL\_FAILURE | | `0x03` | INVALID\_OPERATION | | `0x04` | INVALID\_DATABASE\_ID | | `0x05` | INVALID\_DATA | | `0x06` | KEY\_DOES\_NOT\_EXIST | | `0x07` | DATABASE\_FULL | | `0x08` | DATA\_STALE | | `0x09` | DB\_NOT\_SUPPORTED | | `0x0A` | DB\_LOCKED | | `0x0B` | TRY\_LATER (endpoint not accepting writes yet) | ## Sync commands (endpoint `0xb2db`) Opcodes `0x06`–`0x0C` (DIRTY\_DBS, START\_SYNC, WRITE, WRITEBACK, SYNC\_DONE, VERSION, DIRTY\_ALL) implement watch→phone writeback of dirty records; responses to these set bit 7 of the opcode. VERSION reports the protocol version (currently 1). Message layouts are the packed structs in `endpoint2.c`. ## Database ids | Id | Database | Id | Database | | --- | --- | --- | --- | | `0x00` | Test | `0x07` | Prefs | | `0x01` | Pins | `0x08` | Contacts | | `0x02` | Apps | `0x09` | WatchAppPrefs | | `0x03` | Reminders | `0x0A` | Health | | `0x04` | Notifs | `0x0B` | AppGlance | | `0x05` | Weather | `0x0C` | Settings | | `0x06` | iOSNotifPref | | | New databases register a `BlobDBId` and an entry in `s_blob_dbs` in `fw/services/blob_db/api.c`. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/blob-db.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/blob-db.md). --- # Draw commands (PDC) Source: https://developer.repebble.com/pebbleos/docs/reference/formats/pdc/ Pebble Draw Commands are the vector-graphics format behind `GDrawCommand`: an image (`.pdc` / PDCI) or animation sequence (PDCS) of stroke/fill commands rendered at runtime. The packed structs and file magics are defined in `fw/applib/graphics/gdraw_command_private.h`; files are generated from SVG or JSON by `tools/generate_pdcs/` (the serializers and format docstring live in `tools/generate_pdcs/pebble_commands.py`), and `tools/pdc2png` renders them back to PNG. All multi-byte fields are little-endian. ## Draw command | Field | Type | Notes | | --- | --- | --- | | type | `uint8_t` | 1 = path, 2 = circle, 3 = precise path (0 = invalid) | | flags | `uint8_t` | bit 0: hidden; bits 1–7 reserved | | stroke color | `uint8_t` | ARGB8 | | stroke width | `uint8_t` | | | fill color | `uint8_t` | ARGB8 | | path: open | `uint8_t` ×2 | 1 = open path, 0 = closed; second byte unused | | circle: radius | `uint16_t` | (shares the two bytes above) | | num\_points | `uint16_t` | 1 for circles | | points | `int16_t` ×2 each | x, y per point | Path points are whole pixels; precise-path points are 13.3 fixed-point (eighths of a pixel). A command list is a `uint16_t` command count followed by that many commands. ## Coordinate grid The SVG converters translate input coordinates by (-0.5, -0.5) and round, so source coordinates must sit on the half-pixel grid: multiples of 0.5 px for paths and circles, 0.125 px (1/8) for precise paths. Off-grid points are snapped to the nearest supported coordinate and flagged (`convert_to_pebble_coordinates` in `tools/generate_pdcs/pebble_commands.py` and the annotation emitted by `tools/libs/pblconvert/pblconvert/svg2pdc/pdc.py`). ## File containers **PDCI (image)**: | Field | Type | | --- | --- | | magic | `"PDCI"` | | payload size | `uint32_t` | | version | `uint8_t` (currently 1) | | reserved | `uint8_t` | | view-box width, height | `int16_t` ×2 | | command list | | **PDCS (sequence)** replaces the command list with animation frames: | Field | Type | | --- | --- | | magic | `"PDCS"` | | payload size | `uint32_t` | | version, reserved, view-box | as PDCI | | play count | `uint16_t` | | frame count | `uint16_t` | | frames | `uint16_t` duration (ms) + command list, each | Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/formats/pdc.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/formats/pdc.md). --- # External Source: https://developer.repebble.com/pebbleos/docs/reference/external/ Here you will find a list of relevant external resources related to PebbleOS. - [Pebble OS capabilities](https://docs.google.com/presentation/d/1wfyBRwbrv5YtSnvNRnEPz5tRx9y7VGcFsuHbi1X-D7I/edit?usp=sharing) - [Firmware Development (For Non-Firmware Engineers)](https://docs.google.com/presentation/d/1M--yoEJBO-uckvY5CTFfHT4srw6RCj9RTGT57RcogX8/edit?usp=sharing) - [COREDUMP #003: Pebble’s Code is Free: 3 Former Pebble Engineers Discuss Why It’s Important (Pt. 1/2)](https://www.youtube.com/watch?v=dk5wsNN8abo) Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/external.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/external.md). --- # Font resources (PBF) Source: https://developer.repebble.com/pebbleos/docs/reference/formats/font/ Fonts ship in firmware resources as binary PBF files, generated from TTF input by `tools/font/fontgen.py` and parsed at runtime by `fw/applib/graphics/text_resources.c`. The on-disk structs live in `fw/applib/fonts/fonts_private.h` and `fw/applib/graphics/text_resources.h`; `tools/font/` also contains standalone readers (`dump_font.py`, `pbf_extract.py`, `pbf_repack.py`). A PBF file is four consecutive sections, all multi-byte fields little-endian: 1. `FontInfo` header 2. hash table 3. offset tables 4. glyph table ## FontInfo header “FontInfo” is the generator’s name for the on-disk header; the firmware calls the struct `FontMetaDataV3` (`FontInfo` in `fonts_private.h` is an unrelated runtime struct). | Field | Type | Since | Notes | | --- | --- | --- | --- | | `version` | `uint8_t` | v1 | 1, 2 or 3 | | `max_height` | `uint8_t` | v1 | tallest glyph in the font | | `number_of_glyphs` | `uint16_t` | v1 | | | `wildcard_codepoint` | `uint16_t` | v1 | rendered for missing glyphs; default U+25AF | | `hash_table_size` | `uint8_t` | v2 | bucket count; the generator always writes 255 | | `codepoint_bytes` | `uint8_t` | v2 | 2, or 4 if any codepoint exceeds U+FFFF | | `size` | `uint8_t` | v3 | size of this header (10), for future extension | | `features` | `uint8_t` | v3 | see below | `features` bits (`FEATURE_OFFSET_16` / `FEATURE_RLE4` in `fonts_private.h`): - bit 0: glyph-table offsets are `uint16_t` if set, `uint32_t` if clear. The generator sets it when the glyph table fits in 64 KiB. - bit 1: glyph bitmaps are RLE4-compressed if set, plain bitmaps if clear. - bit 2: glyphs are color glyphs (see Color glyphs below). Cannot be combined with bit 1, and only loads on color displays. - bits 3–7: reserved. ## Hash table `hash_table_size` entries of 4 bytes each (`FontHashTableEntry`): `uint8_t hash`, `uint8_t count`, `uint16_t offset`. A codepoint hashes to bucket `codepoint % hash_table_size`; `offset` is the byte offset of the bucket’s entries within the offset-tables section, and `count` is the number of entries there (collisions are chained contiguously, at most 128 entries per bucket). ## Offset tables Each bucket is a run of `{codepoint, glyph_offset}` entries, sorted by codepoint so the firmware can binary-search. Field widths vary per file: codepoints are 2 or 4 bytes (`codepoint_bytes`), glyph offsets 2 or 4 bytes (`features` bit 0), giving entry sizes of 4–8 bytes (`OffsetTableEntry_2_2` … `_4_4` in `text_resources.h`). ## Glyph table The table begins with one 32-bit zero word, so offset 0 means “glyph not present”. Offsets are byte offsets from the start of the glyph table (version 1 counted 32-bit words instead). Glyphs are deduplicated: several codepoints may share one glyph offset. Each glyph is a packed 5-byte header (`GlyphHeaderData`) followed immediately by bitmap data: | Field | Type | | --- | --- | | `width` | `uint8_t` | | `height` | `uint8_t` | | `left_offset` | `int8_t` | | `top_offset` | `int8_t` | | `horiz_advance` | `int8_t` | With RLE4 enabled, the `height` byte instead stores the number of RLE units (the decoder recovers the height from the decompressed size). Version 1 used a different 8-byte header (`GlyphHeaderDataV1`). Bitmap data is 1 bit per pixel: rows are concatenated unaligned into one continuous bit stream, packed LSB-first into 32-bit words and zero-padded to a multiple of 4 bytes. RLE4 compression is a stream of 4-bit units, two per byte (low nibble first): each unit is `[symbol:1][length:3]`, emitting `length + 1` (1–8) copies of the symbol bit. An odd number of units is padded with a `(0,1)` unit, and the stream is zero-padded to a multiple of 4 bytes. The firmware decompresses in place in the glyph cache; the generator verifies at build time that every glyph is in-place decodable. The decoder-side description lives in `text_resources.c`. ## Color glyphs With `features` bit 2 set, `height` in the glyph header is always the height, and the 1-bit bitmap is replaced by a color body: | Field | Type | Notes | | --- | --- | --- | | `encoding` | `uint8_t` | bits 0–1: log2 of bpp (1/2/4/8), bits 2–3: mode | | `palette_size` | `uint8_t` | 0–16 | | `data_len` | `uint16_t` | bytes of `data` | | `palette` | `GColor8[palette_size]` | ARGB2222, alpha 0 = transparent | | `data` | `uint8_t[data_len]` | pixels, see mode | Modes: - 0, raw: rows of `bpp`-bit palette indices, MSB-first, each row padded to a byte. With 8 bpp there is no palette and each byte is a `GColor8`. - 1, RLE: one byte per run, high nibble `run - 1` (1–16 pixels), low nibble the palette index. Runs continue across rows. - 2, tinted: raw 1 bpp coverage with no palette; set pixels are drawn in the text color, like a regular glyph. The body is zero-padded to a multiple of 4 bytes. The firmware streams it from the resource while drawing instead of caching it, so glyph size is not bound by the glyph cache. `tools/font/pbf_color.py` implements the encoding; `pbf_extract.py` and `pbf_repack.py` round-trip color fonts through RGBA PNGs. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/formats/font.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/formats/font.md). --- # Gesture recognizers (touch SDK) Source: https://developer.repebble.com/pebbleos/docs/reference/recognizers/ Gesture recognizers let a third-party app react to raw touch gestures — tap, pan (single-axis drag) and swipe — on touch-capable hardware. They are the building block for a fully custom scroll or drag interaction, as opposed to the fixed behaviour of the built-in `ScrollLayer` / `MenuLayer`. A recognizer is an opaque object created by one of the constructor functions, attached to a `Window`, and driven by the touch service. When the gesture it watches for progresses, it invokes the `RecognizerEventCb` you supplied with a `RecognizerEvent` describing the transition (`Started`, `Updated`, `Completed`, `Cancelled`). Inside the callback you read the current gesture data with the per-recognizer getters. ## Exported API Constructors (each returns an owned `Recognizer *`): - `tap_recognizer_create(event_cb, user_data)` - `pan_recognizer_create(event_cb, user_data, axis)` — `axis` is `PanAxis_Horizontal` or `PanAxis_Vertical` - `swipe_recognizer_create(event_cb, user_data, direction_mask)` — a bitwise-OR of `SwipeDirection` values Gesture data getters (call from the event callback): - Tap: `tap_recognizer_get_tap_point()` - Pan: `pan_recognizer_get_total_delta()`, `pan_recognizer_get_delta_since_start()`, `pan_recognizer_get_delta_since_prev()`, `pan_recognizer_get_velocity()` - Swipe: `swipe_recognizer_get_direction()`, `swipe_recognizer_get_velocity()` Lifecycle and configuration: - `window_attach_recognizer(window, recognizer)` / `window_detach_recognizer(window, recognizer)` - `recognizer_destroy(recognizer)` — destroys an _un-owned_ recognizer; a recognizer owned by a window is destroyed with the window - `recognizer_set_simultaneous_with(recognizer, cb)` — allow this recognizer to be evaluated at the same time as another - `recognizer_set_fail_after(recognizer, other)` — only evaluate this recognizer once `other` has failed (e.g. run a tap only after a pan fails) ## Custom live scroll example A pan recognizer locked to the vertical axis drives a scroll offset that follows the finger in real time. `pan_recognizer_get_delta_since_start()` is exactly `(0, 0)` at the instant the pan Starts, so the content does not jump when the gesture begins. ``` #include \static Window \*s\_window;static Layer \*s\_content\_layer;static Recognizer \*s\_pan;static int16\_t s\_scroll\_base;// committed offset (updated on Complete)static int16\_t s\_scroll\_offset; // live offset shown while draggingstatic void content\_update\_proc(Layer \*layer, GContext \*ctx) {// Draw your content shifted by s\_scroll\_offset (negative scrolls up).// ... application-specific drawing ...}static void pan\_handler(const Recognizer \*recognizer, RecognizerEvent event) {switch (event) {case RecognizerEvent\_Started:// Nothing to do: delta\_since\_start is (0, 0) here.break;case RecognizerEvent\_Updated: {GPoint d = pan\_recognizer\_get\_delta\_since\_start(recognizer);s\_scroll\_offset = s\_scroll\_base + d.y;layer\_mark\_dirty(s\_content\_layer);break;}case RecognizerEvent\_Completed:// Commit the live offset. You could also kick off inertial scrolling// here using pan\_recognizer\_get\_velocity().s\_scroll\_base = s\_scroll\_offset;break;case RecognizerEvent\_Cancelled:// Roll back to the last committed position.s\_scroll\_offset = s\_scroll\_base;layer\_mark\_dirty(s\_content\_layer);break;}}static void window\_load(Window \*window) {Layer \*root = window\_get\_root\_layer(window);GRect bounds = layer\_get\_bounds(root);s\_content\_layer = layer\_create(bounds);layer\_set\_update\_proc(s\_content\_layer, content\_update\_proc);layer\_add\_child(root, s\_content\_layer);// Create a vertical pan and let the window own it.s\_pan = pan\_recognizer\_create(pan\_handler, NULL, PanAxis\_Vertical);window\_attach\_recognizer(window, s\_pan);// Attaching alone is not enough while the system recognizer set is live: see// "The competition contract" below for opting the window out of the bridge.}static void window\_unload(Window \*window) {// The window owns s\_pan and destroys it; just drop our own layer.layer\_destroy(s\_content\_layer);}int main(void) {s\_window = window\_create();window\_set\_window\_handlers(s\_window, (WindowHandlers) {.load = window\_load,.unload = window\_unload,});window\_stack\_push(s\_window, true);app\_event\_loop();window\_destroy(s\_window);} ``` ## The competition contract Touch input is dispatched to a single, ordered set of recognizers at a time. The system installs a **global system recognizer set** (the back-swipe, the built-in scrolling, etc.) and that set is evaluated **first**. Because the first set to claim a touch sequence wins, simply attaching your own recognizers to a window is **not enough** while the system set is live — the system recognizers consume the gesture before your app ever sees it, and your recognizers sit in a dead zone. To take over touch input, an app must opt out of the system set for its window using `window_set_touch_bridge_disabled()`. With the touch bridge disabled on a window, the system recognizer set **fails on Touchdown** for touches on that window, which lets evaluation fall through to the app’s own recognizers — they then receive the full gesture from the first touch event. The contract in short: 1. System recognizer set is evaluated before app recognizers. 2. While it is live, app recognizers attached to a window never win. 3. `window_set_touch_bridge_disabled()` fails the system set on Touchdown for that window, handing every touch to the app’s recognizers. Note `window_set_touch_bridge_disabled()` is exported in this same SDK revision as the recognizer API above, so a live-scroll app can call it in `window_load` to opt its window out of the system bridge and take over touch input. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/recognizers.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/recognizers.md). --- # History of PULSEv2 Source: https://developer.repebble.com/pebbleos/docs/reference/pulse2/history/ This document describes the history of the Pebble dbgserial console leading up to the design of PULSEv2. ## In The Beginning In the early days of Pebble, the dbgserial port was used to print out log messages in order to assist in debugging the firmware. These logs were plain text and could be viewed with a terminal emulator such as minicom. An interactive prompt was added so that firmware developers and the manufacturing line could interact with the running firmware. The prompt mode could be accessed by pressing CTRL-C at the terminal, and could be exited by pressing CTRL-D. Switching the console to prompt mode suppressed the printing of log messages. Data could be written into the external flash memory over the console port by running a prompt command to switch the console to a special “flash imaging” mode and sending it base64-encoded data. This setup worked well enough, though it was slow and a little cumbersome to use at times. Some hacks were tacked on as time went on, like a “hybrid” prompt mode which allowed commands to be executed without suppressing log messages. These hacks didn’t work terribly well. But it didn’t really matter as the prompt was only used internally and it was good enough to let people get stuff done. ## First Signs of Trouble The problems with the serial console started becoming apparent when we started building out automated integration testing. The test automation infrastructure made extensive use of the serial console to issue commands to simulate actions such as button clicks, inspect the firmware state, install applications, and capture screenshots and log messages. From the very beginning the serial console proved to be very unreliable for test automation’s uses, dropping commands, corrupting screenshots and other data, and hiding log messages. The test automation harness which interacted with the dbgserial port became full of hacks and workarounds, but was still very unreliable. While we wanted to have functional and reliable automated testing, we didn’t have the manpower at the time to improve the serial console for test automation’s use cases. And so test automation remained frustratingly unreliable for a long time. ## PULSEv1 During the development of Pebble Time, the factory was complaining that imaging the recovery firmware onto external flash over the dbgserial port was taking too long and was causing a manufacturing bottleneck. The old flash imaging mode had many issues and was in need of a replacement anyway, and improving the throughput to reduce manufacturing costs finally motivated us to allocate engineering time to replace it. The biggest reason the flash imaging protocol was so slow was that it was extremely latency sensitive. After every 768 data bytes sent, the sender was required to wait for the receiver to acknowledge the data before continuing. USB-to-serial adapter ICs are used at both the factory and by developers to interface the watches’ dbgserial ports to modern computers, and these adapters can add up to 16 ms latency to communications in each direction. The vast majority of the flash imaging time was wasted with the dbgserial port idle, waiting for the sender to receive and respond to an acknowledgement. There were other problems too, such as a lack of checksums. If line noise (which wasn’t uncommon at the factory) corrupted a byte into another valid base64 character, the corruption would go unnoticed and be written out to flash. It would only be after the writing was complete that the integrity was verified, and the entire transfer would have to be restarted from the beginning. Instead of designing a new flash imaging protocol directly on top of the raw dbgserial console, as the old flash imaging protocol did, a link-layer protocol was designed which the new flash imaging protocol would operate on top of. This new protocol, PULSE version 1, provided best-effort multiprotocol datagram delivery with integrity assurance to any applications built on top of it. That is, PULSE allowed applications to send and receive packets over dbgserial, without interfering with other applications simultaneously using the link, with the guarantee that the packets either will arrive at the receiver intact or not be delivered at all. It was designed around the use-case of flash imaging, with the hope that other protocols could be implemented over PULSE later on. The hope was that this was the first step to making test automation reliable. Flash imaging turns out to be rather unique, with affordances that make it easy to implement a performant protocol without protocol features that many other applications would require. Writing to flash memory is an idempotent operation: writing the same bytes to the same flash address _n_ times has the same effect as writing it just once. And writes to different addresses can be performed in any order. Because of these features of flash, each write operation can be treated as a wholly independent operation, and the data written to flash will be complete as long as every write is performed at least once. The communications channel for flash writes does not need to be reliable, only error-free. The protocol is simple: send a write command packet with the target address and data. The receiver performs the write and sends an acknowledgement with the address. If the sender doesn’t receive an acknowledgement within some timeout, it re-sends the write command. Any number of write commands and acknowledgements can be in-flight simultaneously. If a write completes but the acknowledgement is lost in transit, the sender can re-send the same write command and the receiver can naively overwrite the data without issue due to the idempotence of flash writes. The new PULSE flash imaging protocol was a great success, reducing imaging time from over sixty seconds down to ten, with the bottleneck being the speed at which the flash memory could be erased or written. After the success of PULSE flash imaging, attempts were made to implement other protocols on top of it, with varying degrees of success. A protocol for streaming log messages over PULSE was implemented, as well as a protocol for reading data from external flash. There were attempts to implement prompt commands and even an RPC system using dynamically-loaded binary modules over PULSE, but they required reliable and in-order delivery, and implementing a reliable transmission scheme separately for each application protocol proved to be very time-consuming and bug-prone. Other flaws in PULSE became apparent as it came into wider use. The checksum used to protect the integrity of PULSE frames was discovered to have a serious flaw, where up to three trailing 0x00 bytes could be appended to or dropped from a packet without changing the checksum value. This flaw, combined with the lack of explicit length fields in the protocol headers, made it much more likely for PULSE flash imaging to write corrupted data. This was discovered shortly after test automation switched over to PULSE flash imaging. ## Make TA Green Again Around January 2016, it was decided that the issues with PULSE that were preventing test automation from fully dropping use of the legacy serial console would best be resolved by taking the lessons learned from PULSE and designing a successor. This new protocol suite, appropriately enough, is called PULSEv2. It is designed with test automation in mind, with the intention of completely replacing the legacy serial console for test automation, developers and the factory. It is much better at communicating and synchronizing link state, which solves problems that test automation was running into with the firmware crashing and rebooting getting the test harness confused. It uses a standard checksum without the flaws of its predecessor, and packet lengths are explicit. And it is future-proofed by having an option-negotiation mechanism, allowing us to add new features to the protocol while allowing old and new implementations to interoperate. Applications can choose to communicate with either best-effort datagram service (like PULSEv1), or reliable datagram service that guarantees in-order datagram delivery. Having the reliable transport available made it very easy to implement prompt commands over PULSEv2. And it was also surprisingly easy to implement a PULSEv2 transport for the Pebble Protocol, which allows developers and test automation to interact with bigboards using libpebble2 and pebble-tool, exactly like they can with emulators and sealed watches connected to phones. Test automation switched over to PULSEv2 on 2016 May 31. It immediately cut down test run times and, once some bugs got shaken out, measurably improved the reliability of test automation. It also made the captured logs from test runs much more useful as messages were no longer getting dropped. PULSEv2 was made the default for all firmware developers at the end of September 2016. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/pulse2/history.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/pulse2/history.md). --- # PULSE Flash Imaging Source: https://developer.repebble.com/pebbleos/docs/reference/pulse2/flash-imaging/ This document describes the PULSE flash imaging protocol. This protocol was originally designed for use over PULSEv1, but also works over the PULSEv2 Best-Effort transport. The flash imaging protocol is used to write raw data directly to the external flash. It is a message-oriented protocol encapsulated in PULSE frames. The primary goals of the protocol are reliability and speed over high-latency links. - All client-sent commands elicit responses - As much as possible, any command can be resent without corrupting the flashing process. This is to accommodate the situation where the command was received and acted upon but the response was lost, and the client retried the command. - Any notification (server→client message which is not a response to a command) can be lost without holding up the flashing process. There must be a way to poll for the status of all operations which elicit notifications. - Most of the state is tracked by the client. The server only has to maintain a minimal, fixed-size amount of state. > The idempotence of writing to flash is leveraged in the design of this protocol to effectively implement a [Selective Repeat ARQ](https://en.wikipedia.org/wiki/Selective_Repeat_ARQ) with an unlimited window size without requiring the server to keep track of which frames are missing. Any Write Data command to the same location in flash can be repeated any number of times with no ill effects. ## Message format All fields in a message which are more than one octet in length are transmitted least-significant octet (LSB) first. All messages begin with a 1-octet Opcode, followed by zero or more data fields depending on the message. All Address fields are offsets from the beginning of flash. Address and Length fields are specified in units of bytes. ### Client Commands #### 1 - Erase flash region Address: 4 octets Length: 4 octets #### 2 - Write data to flash Address: 4 octets Data: 1+ octets The data length is implied. #### 3 - Checksum flash region Address: 4 octets Length: 4 octets #### 4 - Query flash region geometry Region: 1 octet | Region | Description | | --- | --- | ``` 1 | PRF 2 | Firmware resources ``` #### 5 - Finalize flash region Region: 1 octet Inform the server that writing is complete and perform whatever task is necessary to finalize the data written to the region. This may be a no-op. Region numbers are the same as for the “Query flash region geometry” message. ### Server Responses #### 128 - ACKnowledge erase command Address: 4 octets Length: 4 octets Complete?: 1 octet Complete field is zero if the erase is in progress, nonzero when the erase is complete. #### 129 - ACKnowledge write command Address: 4 octets Length: 4 octets Complete?: 1 octet #### 130 - Checksum result Address: 4 octets Length: 4 octets Checksum: 4 octets The legacy Pebble checksum (“STM32 CRC”) of the specified memory is returned. #### 131 - Flash region geometry Region: 1 octet Address: 4 octets Length: 4 octets A length of zero indicates that the region does not exist. #### 132 - ACKnowledge finalize flash region command Region: 1 octet #### 192 - Malformed command Bad message: 9 octets Error string: 0+ octets #### 193 - Internal error Error string: 0+ octets Something has gone terribly wrong which prevents flashing from proceeding. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/pulse2/flash-imaging.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/pulse2/flash-imaging.md). --- # PULSEv2 Protocol Suite Source: https://developer.repebble.com/pebbleos/docs/reference/pulse2/pulse2/ ## Motivation The initial design of PULSE was shaped by its initial use case of flash imaging. Flash imaging has a few properties which allowed it to be implemented on top of a very simplistic wire protocol. Writing to flash can be split up into any number of atomic write operations that can be applied in arbitrary order. Flash writes are idempotent: repeatedly writing the same data to the same flash address does not corrupt the written data. Because of these properties, it was possible to implement the flash imaging protocol in a stateless manner simply by ensuring that every write was applied at least once without concern for out of order delivery or duplicated datagrams. The PULSE link layer was designed as simply as possible, guaranteeing only datagram integrity with only best-effort reliability and sequencing, since it was all that the flash imaging protocol needed. As we try to use PULSE for more applications, it has become clear that flash imaging is a special case. Most applications have some manner of statefulness or non-idempotent operations, so they need guarantees about reliable delivery and sequencing of datagrams in order to operate correctly in the face of lost or corrupted datagrams. The lack of such guarantees in PULSE has forced these applications to bake sequencing and retransmissions into the application protocols in an ad-hoc manner, poorly. This has made the design and implementation of prompt and file transfer protocols more complex than necessary, and no attempt has yet been made to tunnel Pebble Protocol over PULSE. It’s the [waterbed theory](https://wiki.c2.com/?WaterbedTheory) at work. Adding support for reliable, ordered delivery of datagrams will allow for any application to make use of reliable service simply by requesting it. Implementation of chatty protocols will be greatly simplified. ## Protocol Stack PULSEv2 is a layered protocol stack. The link layer provides integrity-assured delivery of packet data. On top of the link layer is a suite of transport protocols which provide multiprotocol delivery of application datagrams with or without guaranteed reliable in-order delivery. Application protocols use one or more of the available transports to exchange datagrams between the firmware running on a board and a host workstation. ## Physical Layer PULSEv2 supports asynchronous serial byte-oriented full duplex links, 8-N-1, octets transmitted LSB first. The link must transparently pass all octet values. The baud rate is 1,000,000 bps. > **Why that baud rate?** > > 1 Mbaud is a convenient choice as it is the highest frequency which divides perfectly into a 16 MHz core clock at 16x oversampling, and works with zero error at 64, 80 and 100 MHz (with only 100 MHz requiring any fractional division at all). The only downside is that it is not a “standard” baud rate, but this is unlikely to be a problem as FTDI, PL2303, CP2102 (but not CP2101) and likely others will handle 1 Mbaud rates (at least in hardware). YMMV with Windows drivers… ## Link Layer The link layer, in a nutshell, is PPP with custom framing. The entirety of [RFC 1661](https://datatracker.ietf.org/doc/html/rfc1661) is normative, except as noted in this document. ### Encapsulation PPP encapsulation (RFC 1661, Section 2) is used. The Padding field of the PPP encapsulation must be empty. A summary of the frame structure is shown below. This figure does not include octets inserted for transparency. The fields are transmitted from left to right. | Flag | Protocol | Information | FCS | Flag | | --- | --- | --- | --- | --- | | 0x55 | 2 octets | \* | 4 octets | 0x55 | #### Flag field Each frame begins and ends with a Flag sequence, which is the octet 0x55 hexadecimal. The flag is used for frame synchronization. > **Why 0x55?** > > It is transmitted as bit pattern `(1)0101010101`, which is really easy to spot on an oscilloscope trace or logic analyzer capture, and it allows for auto baud rate detection. The STM32F7 USART supports auto baud rate detection with an 0x55 character in hardware. Only one Flag sequence is required between two frames. Two consecutive Flag sequences constitute and empty frame, which is silently discarded. #### Protocol field The Protocol field is used as prescribed by RFC 1661, Section 2. PPP assigned protocol numbers and their respective assigned protocols should be used wherever it makes sense. Custom protocols must not be assigned protocol numbers which overlap any [existing PPP assigned protocol](https://www.iana.org/assignments/ppp-numbers). #### Frame Check Sequence field The Frame Check Sequence is transmitted least significant octet first. The check sequence is calculated using the [CRC-32](https://reveng.sourceforge.net/crc-catalogue/all.htm#crc.cat.crc-32) checksum. The parameters of the CRC algorithm are: ``` width=32 poly=0x04c11db7 init=0xffffffff refin=true refout=true xorout=0xffffffff check=0xcbf43926 name="CRC-32" ``` The FCS field is calculated over all bits of the Protocol and Information fields, not including any start and stop bits, or octets inserted for transparency. This also does not include the Flag sequence nor the FCS field itself. ### Transparency Transparency is achieved by applying [COBS](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing) encoding to the Protocol, Information and FCS fields, then replacing any instances of 0x55 in the COBS-encoded data with 0x00. ### Link Operation The Link Control Protocol packet format, assigned numbers and state machine are the same as PPP (RFC 1661), with minor exceptions. > Do not be put off by the length of the RFC document. Only a small subset of the protocol needs to be implemented (especially if there are no negotiable options) for an implementation to be conforming. > All multi-byte fields in LCP packets are transmitted in Network (big-endian) byte order. The burden of converting from big-endian to little-endian is very minimal, and it lets Wireshark dissectors work on PULSEv2 LCP packets just like any other PPP LCP packet. By prior agreement, peers MAY transmit or receive packets of certain protocols while the link is in any phase. This is contrary to the PPP standard, which requires that all non-LCP packets be rejected before the link reaches the Authentication phase. ## Transport Layer ### Best-Effort Application Transport (BEAT) protocol Best-effort delivery with very little overhead, similar to PULSEv1. #### Packet format | Application Protocol | Length | Information | | --- | --- | --- | | 2 octets | 2 octets | \* | All multibyte fields are in big-endian byte order. The Length field encodes the number of octets in the Application Protocol, Length and Information fields of the packet. The minimum value of the Length field in a valid packet is 4. BEAT application protocol 0x0001 is assigned to the PULSE Control Message Protocol (PCMP). When a BEAT packet is received by a conforming implementation with the Application Protocol field set to an unrecognized value, a PCMP Unknown Protocol message MUST be sent. #### BEAT Control Protocol (BECP) BECP uses the same packet exchange mechanism as the Link Control Protocol. BECP packets may not be exchanged until LCP is in the Opened state. BECP packets received before this state is reached should be silently discarded. BECP is exactly the same as the Link Control Protocol with the following exceptions: - Exactly one BECP packet is encapsulated in the Information field of Link Layer frames where the Protocol field indicates type 0xBA29 hex. - Only codes 1 through 7 (Configure-Request, Configure-Ack, Configure-Nak, Configure-Reject, Terminate-Request, Terminate-Ack and Code-Reject) are used. Other codes should be treated as unrecognized and should result in Code-Rejects. - A distinct set of configure options are used. There are currently no options defined. #### Sending BEAT packets Before any BEAT protocol packets may be communicated, both LCP and BECP must reach the Opened state. Exactly one BEAT protocol packet is encapsulated in the Information field of Link Layer frames where the Protocol field indicates type 0x3A29 hex. ### PUSH (Simplex) transport Simplex best-effort delivery of datagrams. It is designed for log messages and other status updates from the firmware to the host. There is no NCP, no options, no negotiation. #### Packet format | Application Protocol | Length | Information | | --- | --- | --- | | 2 octets | 2 octets | \* | All multibyte fields are in big-endian byte order. The Length field encodes the number of octets in the Application Protocol, Length and Information fields of the packet. The minimum value of the Length field in a valid packet is 4. #### Sending PUSH packets Packets can be sent at any time regardless of the state of the link, including link closed. Exactly one PUSH packet is encapsulated in the Information field of Link Layer frames where the Protocol field indicates type 0x5021 hex. ### Reliable transport (TRAIN) The Reliable transport provides reliable in-order delivery service of multiprotocol application datagrams. The protocol is heavily based on the [ITU-T Recommendation X.25](https://www.itu.int/rec/T-REC-X.25-199610-I/en) LAPB data-link layer. The remainder of this section relies heavily on the terminology used in Recommendation X.25. Readers are also assumed to have some familiarity with section 2 of the Recommendation. #### Packet formats The packet format is, in a nutshell, LAPB in extended mode carrying BEAT packets. **Information command packets** | Control | Application Protocol | Length | Information | | --- | --- | --- | --- | | 2 octets | 2 octets | 2 octets | \* | **Supervisory commands and responses** | Control | | --- | | 2 octets | ##### Control field The control field is basically the same as LAPB in extended mode. Only Information transfer and Supervisory formats are supported. The Unnumbered format is not used as such signalling is performed out-of-band using the TRCP control protocol. The Information command and the Receive Ready, Receive Not Ready, and Reject commands and responses are permitted in the control field. The format and meaning of the subfields in the Control field are described in ITU-T Recommendation X.25. ##### Application Protocol field The protocol number for the message contained in the Information field. This field is only present in Information packets. The Application Protocol field is transmitted most-significant octet first. ##### Length field The Length field specifies the number of octets covering the Control, Application Protocol, Length and Information fields. The Length field is only present in Information packets. The content of a valid Information packet must be no less than six. The Length field is transmitted most-significant octet first. ##### Information field The application datagram itself. This field is only present in Information packets. #### TRAIN Control Protocol The TRAIN Control Protocol, or TRCP for short, is used to set up and tear down the communications channel between the two peers. TRCP uses the same packet exchange mechanism as the Link Control Protocol. TRCP packets may not be exchanged until LCP is in the Opened state. TRCP packets received before this state is reached should be silently discarded. TRCP is exactly the same as the Link Control Protocol with the following exceptions: - Exactly one TRCP packet is encapsulated in the Information field of Link Layer frames where the Protocol field indicates type 0xBA33 hex. - Only codes 1 through 7 (Configure-Request, Configure-Ack, Configure-Nak, Configure-Reject, Terminate-Request, Terminate-Ack and Code-Reject) are used. Other codes should be treated as unrecognized and should result in Code-Rejects. - A distinct set of configure options are used. There are currently no options defined. The `V(S)` and `V(R)` state variables shall be reset to zero when the TRCP automaton signals the This-Layer-Up event. All packets in the TRAIN send queue are discarded when the TRCP automaton signals the This-Layer-Finished event. #### LAPB system parameters The LAPB system parameters used in information transfer have the default values described below. Some parameter values may be altered through the TRCP option negotiation mechanism. (NB: there are currently no options defined, so there is currently no way to alter the default values during the protocol negotiation phase) **Maximum number of bits in an I packet _N1_** is equal to eight times the MRU of the link, minus the overhead imposed by the Link Layer framing and the TRAIN header. This parameter is not negotiable. **Maximum number of outstanding I packets _k_** defaults to 1 for both peers. This parameter is (to be) negotiable. If left at the default, the protocol will operate with a Stop-and-Wait ARQ. #### Transfer of application datagrams Exactly one TRAIN packet is encapsulated in the Information field of Link Layer frames. A command packet is encapsulated in a Link Layer frame where the Protocol field indicates 0x3A33 hex, and a response packet is encapsulated in a Link Layer frame where the Protocol field indicates 0x3A35 hex. Transfer of datagrams shall follow the procedures described in Recommendation X.25 §2.4.5 _LAPB procedures for information transfer_. A cut-down set of procedures for a compliant implementation which only supports _k=1_ operation can be found in [reliable-transport.md](https://developer.repebble.com/pebbleos/docs/reference/pulse2/reliable-transport.md). In the event of a frame rejection condition (as defined in Recommendation X.25), the TRCP automaton must be issued a Down event followed by an Up event to cause an orderly renegotiation of the transport protocol and reset the state variables. This is the same as the Restart option described in RFC 1661. A FRMR response MUST NOT be sent. TRAIN application protocol 0x0001 is assigned to the PULSE Control Message Protocol (PCMP). When a TRAIN packet is received by a conforming implementation with the Application Protocol field set to an unrecognized value, a PCMP Unknown Protocol message MUST be sent. ### PULSE Control Message Protocol The PULSE Control Message Protocol (PCMP) is used for signalling of control messages by the transport protocols. PCMP messages must be encapsulated in a transport protocol, and are interpreted within the context of the encapsulated transport protocol. > **Why a separate protocol?** > > Many of the transports need to communicate the same types of control messages. Rather than defining a different way of communicating these messages for each protocol, they can use PCMP and share a single definition (and implementation!) of these messages. #### Packet format | Code | Information | | --- | --- | | 1 octet | \* | #### Defined codes ##### 1 - Echo Request When the transport is in the Opened state, the recipient MUST respond with an Echo-Reply packet. When the transport is not Opened, any received Echo-Request packets MUST be silently discarded. ##### 2 - Echo Reply A reply to an Echo-Request packet. The Information field MUST be copied from the received Echo-Request. ##### 3 - Discard Request The receiver MUST silently discard any Discard-Request packet that it receives. ##### 129 - Port Closed A packet has been received with a port number unrecognized by the recipient. The Information field must be filled with the port number copied from the received packet (without endianness conversion). ##### 130 - Unknown PCMP Code A PCMP packet has been received with a Code field which is unknown to the recipient. The Information field must be filled with the Code field copied from the received packet. * * * ## Useful Links - [The design document for PULSEv2](https://docs.google.com/a/pulse-dev.net/document/d/1ZlSRz5-BSQDsmutLhUjiIiDfVXTcI53QmrqENJXuCu4/edit?usp=sharing), which includes a draft of this documentation along with a lot of notes about the design decisions. - [Python implementation of PULSEv2](https://github.com/coredevices/PebbleOS/tree/main/tools/libs/pulse2) - [RFC 1661 - The Point to Point Protocol (PPP)](https://datatracker.ietf.org/doc/html/rfc1661) - [RFC 1662 - PPP in HDLC-like Framing](https://datatracker.ietf.org/doc/html/rfc1662) - [RFC 1663 - PPP Reliable Transmission](https://datatracker.ietf.org/doc/html/rfc1663) - [RFC 1570 - PPP LCP Extensions](https://datatracker.ietf.org/doc/html/rfc1570) - [RFC 2153 - PPP Vendor Extensions](https://datatracker.ietf.org/doc/html/rfc2153) - [RFC 3772 - Point-to-Point Protocol (PPP) Vendor Protocol](https://datatracker.ietf.org/doc/html/rfc3772) - [PPP Consistent Overhead Byte Stuffing (COBS)](https://datatracker.ietf.org/doc/html/draft-ietf-pppext-cobs) - [ITU-T Recommendation X.25](https://www.itu.int/rec/T-REC-X.25-199610-I/en) - [Digital Data Communications Message Protocol](https://www.ibiblio.org/pub/historic-linux/early-ports/Mips/doc/DEC/ddcmp-4.1.txt) Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/pulse2/pulse2.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/pulse2/pulse2.md). --- # PULSEv2 Reliable Transport Source: https://developer.repebble.com/pebbleos/docs/reference/pulse2/reliable-transport/ The purpose of this document is to describe the procedures for the PULSEv2 reliable transport (TRAIN) to be used in the initial implementations, with support for only Stop-and-Wait ARQ (Automatic Repeat reQuest). Hopefully, limiting the scope in this way will make it simpler to implement compared to a more performant Go-Back-N ARQ. This document is a supplement to the description of TRAIN in [pulse2.md](https://developer.repebble.com/pebbleos/docs/reference/pulse2/pulse2.md). The PULSEv2 reliable transport (TRAIN) is based on X.25 LAPB, which implements reliable datagram delivery using a Go-Back-N ARQ (Automatic Repeat reQuest) procedure. Since a Stop-and-Wait ARQ is equivalent to Go-Back-N with a window size of 1, LAPB can be readily adapted for Stop-and-Wait ARQ. The description in this document should hopefully be compatible with an implementation supporting the full Go-Back-N LAPB procedures when that implementation is configured with a window size of 1, so that there is a smooth upgrade path which doesn’t require special cases or compatibility breakages. ## Documentation conventions This document relies heavily on the terminology used in [ITU-T Recommendation X.25](https://www.itu.int/rec/T-REC-X.25-199610-I/en). Readers are also assumed to have some familiarity with section 2 of that document. The term “station” is used in this document to mean “DCE or DTE”. ## Procedures for information transfer There is no support for communicating a busy condition. It is assumed that a station in a busy condition will silently drop packets, and that the timer recovery procedure will be sufficient to ensure reliable delivery of the dropped packets once the busy condition is cleared. An implementation need not support sending or receiving RNR packets. ## Sending I packets All Information transfer packets must be sent with the Poll bit set to 1. The procedures from X.25 §2.4.5.1 apply otherwise. ## Receiving an I packet When the DCE receives a valid I packet whose send sequence number N(S) is equal to the DCE receive state variable V(R), the DCE will accept the information fields of this packet, increment by one its receive state variable V(R), and transmit an RR response packet with N(R) equal to the value of the DCE receive state variable V(R). If the received I packet has the Poll bit set to 1, the transmitted RR packet must be a response packet with Final bit set to 1. Otherwise the transmitted RR packet should have the Final bit set to 0. ## Reception of out-of-sequence I packets Since the DTE should not have more than one packet in-flight at once, an out-of-sequence I packet would be due to a retransmit: RR response for the most recently received I packet got lost, so the DTE re-sent the I packet. Discard the information fields of the packet and send an RR packet with N(R)=V(R). ## Receiving acknowledgement When correctly receiving a RR packet, the DCE will consider this packet as an acknowledgement of the most recently-sent I packet if N(S) of the most recently-sent I packet is equal to the received N(R)-1. The DCE will stop timer T1 when it correctly receives an acknowledgement of the most recently-sent I packet. Since all I packets are sent with P=1, the receiving station is obligated to respond with a supervisory packet. Therefore it is unnecessary to support acknowledgements embedded in I packets. ## Receiving an REJ packet Since only one I packet may be in-flight at once, the REJ packet is due to the RR acknowledgement from the DTE getting lost and the DCE retransmitting the I packet. Treat it like an RR. ## Waiting acknowledgement The DCE maintains an internal transmission attempt variable which is set to 0 when the transport NCP signals a This-Layer-Up event, and when the DCE correctly receives an acknowledgement of a sent I packet. If Timer T1 runs out waiting for the acknowledgement from the DTE for an I packet transmitted, the DCE will add one to its transmission attempt variable, restart Timer T1 and retransmit the unacknowledged I packet. If the transmission attempt variable is equal to N2 (a system parameter), the DCE will initiate a restart of the transport link. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/pulse2/reliable-transport.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/pulse2/reliable-transport.md). --- # Reference Source: https://developer.repebble.com/pebbleos/docs/reference/ Lookup material: the firmware C API reference, external talks and documents about PebbleOS, and specifications for the protocols used by the firmware and its tooling. ## C API reference The Doxygen-generated reference for the firmware sources (applib, services, kernel, drivers) is published alongside this site: [PebbleOS API reference](https://developer.repebble.com/pebbleos/apidoc/index.html) ## App SDK guides Hand-written guides for app-facing APIs that need more than the per-function Doxygen reference. - [Gesture recognizers (touch SDK)](https://developer.repebble.com/pebbleos/docs/reference/recognizers.md) ## External resources Podcasts, presentations and developer documents from the Pebble community. - [External](https://developer.repebble.com/pebbleos/docs/reference/external.md) ## PULSEv2 protocol suite PULSE is the serial protocol spoken between the firmware and the host tooling (`pbl console`, flash imaging, `tools/pulse/`). These pages specify the wire format and its transports. - [PULSEv2 Protocol Suite](https://developer.repebble.com/pebbleos/docs/reference/pulse2/pulse2.md) - [PULSEv2 Reliable Transport](https://developer.repebble.com/pebbleos/docs/reference/pulse2/reliable-transport.md) - [PULSE Flash Imaging](https://developer.repebble.com/pebbleos/docs/reference/pulse2/flash-imaging.md) - [History of PULSEv2](https://developer.repebble.com/pebbleos/docs/reference/pulse2/history.md) ## Pebble Protocol endpoints Wire formats of the Bluetooth protocol spoken between the firmware and the phone apps. - [BlobDB endpoint](https://developer.repebble.com/pebbleos/docs/reference/blob-db.md) ## Binary formats The file and resource formats produced by the firmware tooling. These pages summarize the layouts; the packed structs in the referenced sources stay authoritative. - [Font resources (PBF)](https://developer.repebble.com/pebbleos/docs/reference/formats/font.md) - [Bitmap resources (PBI)](https://developer.repebble.com/pebbleos/docs/reference/formats/pbi.md) - [Draw commands (PDC)](https://developer.repebble.com/pebbleos/docs/reference/formats/pdc.md) - [Timezone database](https://developer.repebble.com/pebbleos/docs/reference/formats/timezone.md) Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/index.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/index.md). --- # Timezone database Source: https://developer.repebble.com/pebbleos/docs/reference/formats/timezone/ The firmware’s timezone list is a binary resource (`RESOURCE_ID_TIMEZONE_DATABASE`) built from an IANA tzdata snapshot (`resources/normal/base/tzdata/timezones_olson.txt`) by `tools/timezones.py`, wired into the resource build by `tools/cmake/resources.py`. It is read by `fw/services/timezone_database/service.c`; the DST-rule struct is `TimezoneDSTRule` in `include/pbl/services/timezone_database.h`. Layout (little-endian): a 6-byte header, then region records, DST rules and link records. ## Header `uint16_t region_count`, `uint16_t dst_rule_count`, `uint16_t link_count`. ## Region records — 24 bytes each | Field | Size | Notes | | --- | --- | --- | | continent index | 1 | into a fixed 10-entry list: Africa, America, Antarctica, Asia, Atlantic, Australia, Europe, Indian, Pacific, Etc | | city name | 15 | NUL-padded; full name is `continent/city` | | GMT offset | 2 | `int16_t`, minutes | | tz abbreviation | 5 | NUL-padded; may contain `*` as a `%s` placeholder for the D/S letter | | DST rule id | 1 | 0 = no DST | ## DST rules — 16 bytes per id Each DST id owns a pair of 8-byte `TimezoneDSTRule` records, start rule then end rule: | Field | Size | Notes | | --- | --- | --- | | `ds_label` | 1 | `'D'` enter DST, `'S'` leave, `'\0'` none | | `wday` | 1 | 0 = Sunday; 255 = any day | | `flag` | 1 | bit 0: “last ” (day-decrement); bit 1: rule time is standard time; bit 2: UTC; bit 3: wall time (written by the builder, never read — wall time is the firmware default) | | `month` | 1 | 0-based | | `mday` | 1 | 1-based | | `hour`, `minute` | 2 | | | padding | 1 | | Rule id 0 (“no DST”) is counted in `dst_rule_count` but not stored, so rule id _n_ lives at `(n - 1) × 16` bytes into the section. The mapping from tzdata rule names to ids (`dstzone_list` in `tools/timezones.py`) is append-only: the firmware hardcodes some ids (e.g. Brazil = 6), so the order must never change between database releases. ## Link records — 35 bytes each `uint16_t region_id` plus a 33-byte NUL-padded alias (sized for the longest IANA name). Links resolve legacy aliases — old mobile OSes still send names like `US/Pacific` — to a region record. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/reference/formats/timezone.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/reference/formats/timezone.md). --- # Build system Source: https://developer.repebble.com/pebbleos/docs/development/build_system/ The firmware is built with [CMake](https://cmake.org), following a model borrowed from Zephyr. Ninja is the generator the project uses: ``` pbl configure --board asterix pbl build ``` `pbl configure` runs CMake with `-GNinja` into `build/`, and passes anything else through, so Kconfig symbols are overridden the usual way: ``` pbl configure --board obelix@pvt --variant prf -DCONFIG_RELEASE=y ``` `pbl` itself is documented in [The pbl CLI](https://developer.repebble.com/pebbleos/docs/development/pbl.md). Plain CMake works just as well; it only saves the typing: ``` cmake -B build -GNinja -DBOARD=asterix cmake --build build ``` The unit tests are a CMake project of their own – they build for the host, not for the watch – out of their own build directory; see [Running and writing tests](https://developer.repebble.com/pebbleos/docs/development/testing.md). ## The library model Each directory that contributes code declares a library and feeds it with source files: ``` pbl\_library()pbl\_library\_sources(service.c util.c) ``` - Every declared library ends up in the single firmware image, so nothing has to list them. Dependencies between firmware libraries do not need declaring either: they resolve at the final link. - The library is named after its directory (`fw/services/timeline` → `fw __services__ timeline`). Give it a name of its own with `pbl_library_named()` when something refers to it, or when one directory builds more than one library. - The target is only created once the library gets its first source file, so a directory whose sources are all configured out contributes nothing. `pbl_library_kind(stlib)` builds a static library the linker pulls objects from on demand, instead of the default `objects`, whose every object file is linked. Use it for third-party code that ships more than the firmware references. Private compile settings use the matching per-library calls: ``` pbl\_library\_include\_directories(.)pbl\_library\_compile\_definitions(HAVE\_CONFIG\_H)pbl\_library\_compile\_options(-Wno-sign-compare) ``` Conditional pieces key off the Kconfig symbols, which CMake sees as ordinary variables: ``` pbl\_library\_sources\_ifdef(CONFIG\_ACCEL\_LSM6DSO lsm6dso/lsm6dso.c)pbl\_add\_subdirectory\_ifdef(CONFIG\_MIC mic) ``` ## Exposing headers Register the directory the headers live in: ``` pbl\_include\_directories(include)pbl\_compile\_definitions(FOO\_ENABLED=1) ``` Paths are relative to the calling `CMakeLists.txt`, and the matching build directory is added too, so generated headers are found next to their hand-written neighbours. Headers are global; there is no per-consumer opt-in. A library that is not always needed is gated behind a Kconfig symbol instead, as Zephyr does: its directory is added with `pbl_add_subdirectory_ifdef()`, and consumers `select` (or `imply`) the symbol in their own Kconfig, e.g. `BT` selects `MBEDTLS` and `SERVICE_VOICE` selects `SPEEX`. Prebuilt vendor archives are registered with `pbl_library_import(name path...)`, which looks for `lib.a`. ## Linker script fragments Libraries that need linker script additions register them against one of the master script’s hook points: ``` pbl\_linker\_sources(memory linker.ld) ``` See `cmake/modules/linker.cmake` for the available hooks. Sources that must run from RAM are marked with `pbl_library_ramfunc(file.c ...)`; see [the kernel](https://developer.repebble.com/pebbleos/docs/architecture/kernel.md) for the `.ramfunc` section. ## Generated sources Generated headers are produced by custom commands and collected under the `pbl_generated_headers` target, which every library waits for. Generators live in `tools/cmake/`: - `kconfig.py` — runs Kconfig, writes `.config`, `autoconf.h` and the CMake variables the build reads. - `resources.py` — resolves the board’s resource maps into build rules, in batches so that a few hundred resources do not cost a few hundred interpreter start-ups, and runs the resource, pbpack and resource-table steps. - `generate.py` — the remaining source generators (version header, app registry, protocol endpoint table, applib allocator tables, log string hashing, stored apps). - `firmware.py` — image steps: size checks, bundling, QEMU flash images. `autoconf.h` is force-included into every compilation, so Kconfig symbols reach the sources, the headers and the linker script alike. ## PebbleOS SDK `cmake/modules/pebbleos_sdk.cmake` locates the installed SDK once and caches it as `PEBBLEOS_SDK_ROOT`. Every tool lookup then uses the SDK as its first search path: the toolchain in `cmake/toolchain/arm-none-eabi.cmake`, and `PBL_QEMU`, `PBL_SFTOOL` and `PBL_GDB` for the `pbl` device commands, which read them back from `CMakeCache.txt`. Each is an ordinary `find_program` result, so it is cached too and can be overridden with `-D`. ## Directory layout ``` CMakeLists.txtthefirmwareimage:whatisbuiltandlinkedcmake/toolchain/thebare-metalARMtoolchaincmake/modules/thebuildmodel,splitbyconcerntools/cmake/thegeneratorsthebuildshellsoutto ``` The one build system left besides CMake is the waf under `sdk/`, which is not used to build anything here: it is packaged into the SDK for app developers to build their own projects with. See [Exposing functions to the SDK](https://developer.repebble.com/pebbleos/docs/development/sdk_export.md). Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/build\_system.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/build_system.md). --- # Building firmware Source: https://developer.repebble.com/pebbleos/docs/development/building_fw/ Before building, make sure you’ve configured [pbl](https://developer.repebble.com/pebbleos/docs/development/options.md). Then, run the following: ``` pbl build ``` ## Loading firmware with a firmware development kit Before attempting to flash, check the documentation for each [board](https://developer.repebble.com/pebbleos/docs/boards.md) on how to prepare and connect your watch for programming. You can flash the built firmware by running: ``` pbl flash ``` In some cases, you may have to specify the `--tty $SERIAL_ADAPTER` option where `$SERIAL_ADAPTER` is the path for your serial adapter, e.g. `/dev/ttyACM0`, `/dev/tty.usbmodem1102`, etc. If flashing for the first time, you will also need to flash resources. Some boards support direct resource programming by passing the `--resources` option. The alternative is to flash while the firmware is running via the serial port using: ``` pbl image_resources --tty $SERIAL\_ADAPTER ``` When both firmware and resources are flashed, you should observe the watch booting into the main application. You can also see the logs by opening the console: ``` pbl console --tty $SERIAL\_ADAPTER ``` Try sending `help` to get a list of available console commands. ## Loading firmware via Bluetooth If you don’t have a firmware development kit, you may bundle a `.pbz` file for sideloading onto your sealed watch: ``` pbl bundle ``` The resulting `.pbz` file will be located in the `build/` directory. Transfer this file to the device paired to your watch, then, in the Pebble app, enable `Settings -> Show debug options`. Go back to the Devices tab, tap your watch, then `Firmware Update Debug -> Sideload FW`, and select the `.pbz` file. On Android, flashing repeatedly is better scripted over adb, which installs without asking. This route is open to an adb shell, which holds the `android.permission.DUMP` the broadcast requires, and it needs `Show debug options` on in the app. `$PBZ` is the file from `build/`: ``` adb push $PBZ /data/local/tmp/firmware.pbz adb shell am broadcast -a coredevices.pebble.SIDELOAD_FIRMWARE \-n coredevices.coreapp/coredevices.pebble.firmware.FirmwareSideloadReceiver \--es path /data/local/tmp/firmware.pbz ``` The app waits up to a minute for the watch to be connected, then starts the update and shows its progress on the watch card in the Devices tab. Not every Android version lets an app read `/data/local/tmp`. If the app reports the file as missing, create `/sdcard/Android/data/coredevices.coreapp/cache` and push it there instead. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/building\_fw.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/building_fw.md). --- # Configuration Options Source: https://developer.repebble.com/pebbleos/docs/development/options/ When configuring the build (`pbl configure ...`) there are several options you can enable or tune. Below you will find a list of the most relevant ones. ## Choosing your target There are a number of target boards to choose from when building PebbleOS. You can do so by using the (`--board`) flag followed by: `asterix`: (Core Devices) Pebble 2 Duo `obelix@bb2`, `obelix@dvt`, `obelix@pvt`: (Core Devices) Pebble Time 2 `getafix@dvt`, `getafix@dvt2`: (Core Devices) Pebble Round 2 `qemu_emery`, `qemu_flint`, `qemu_gabbro`: dedicated QEMU targets (see [QEMU](https://developer.repebble.com/pebbleos/docs/development/qemu.md)) Keep in mind that some targets may not currently compile as-is. ## Variant `--variant`: Build variant, `normal` (main firmware) or `prf` (recovery firmware). ## Release build `-DCONFIG_RELEASE=y`: Build a release-mode firmware. Strips debug aids, enables shipping defaults, and reduces battery usage compared to a debug build. Pass this to `pbl configure`. ## Main features `-DCONFIG_MODDABLE_XS=y` / `-DCONFIG_MODDABLE_XS=n`: Force the Moddable SDK’s XS JavaScript engine on or off, overriding the board defconfig. Pass to `pbl configure`. See [Moddable JS Engine](https://developer.repebble.com/pebbleos/docs/development/moddable.md). PRF (recovery) builds always disable the engine regardless of this value. ## Manufacturing `-DCONFIG_MFG=y`: Enable manufacturing-only functionality in the PRF build. ## Debugging `-DCONFIG_WATCHDOG=n`: Build without the hardware watchdog driver. Software failures then halt with interrupts disabled for a debugger instead of rebooting, and the task watchdog only logs the stalls it detects. `-DCONFIG_DEBUG_INFO_MACROS=y`: Compile with `-g3` rather than `-g`, so a debugger can expand the firmware’s macros. It costs around 7% of the compile time and makes the objects, and with them the link, noticeably larger. `-DCONFIG_LINKER_MAP=y`: Write `build/pebbleos.map`, the cross-referenced link map that `tools/analyze_fw_static_memory_usage.py` reads. It is tens of megabytes and costs around a fifth of the link. ## Flashing The `flash`, `run` and `debug` commands talk to a connected device through a _runner_. Each board declares its supported runners in its board manifest (e.g. `boards//.yml`) and the first one is used by default. For the OpenOCD runner, the probe and target configuration lives in the board’s `support/openocd.cfg`. `--runner`: Override the board’s default runner for `flash`/`run`/`debug`. `--tty`: Serial port used by the `sftool` runner. `--resources`: Also flash system resources alongside the firmware (`sftool` runner). ## Logging `-DCONFIG_DEFAULT_LOG_LEVEL_=y`: Default log level, where `` is one of `ERROR`, `WARNING`, `INFO` (default), `DEBUG` or `DEBUG_VERBOSE`. `-DCONFIG_LOG_HASHED=n`: Disable log messages hashing. This will increase ROM usage, but will not require a dictionary file to decode logs. `-DCONFIG__LOG_LEVEL_RUNTIME=y`: Keep all log messages of a module in the image and filter them at runtime, using `CONFIG__LOG_LEVEL` as the initial level. The level is changed with `PBL_LOG_MODULE_LEVEL_SET(name, level)` and read with `PBL_LOG_MODULE_LEVEL_GET(name)`, where `name` is the one given to `PBL_LOG_MODULE_DEFINE`. Messages accepted by the module level skip the serial and flash log level filters. This will increase ROM usage for that module. The **Settings → System → Debugging → Vibe Log Info** preference sets the alert UI, vibe score, and vibe pattern modules to DEBUG, so their traces reach bug reports. Normal firmware enables runtime levels for these modules by default. Disabling the preference restores each module’s configured level; the preference is also applied at boot. Recovery firmware keeps compile-time logging by default. These and many more options can also be browsed and changed interactively with `pbl menuconfig` after configuring. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/options.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/options.md). --- # Contribution Guidelines Source: https://developer.repebble.com/pebbleos/docs/development/contributing/ Thank you for your interest in contributing to PebbleOS! We welcome contributions of all kinds, including bug fixes, new features, documentation improvements, and more. To ensure a smooth contribution process, please follow the guidelines outlined below. Developer documentation — building the firmware, running it in QEMU, exporting SDK functions, and more — lives in the `docs/` directory and is published at [https://pebbleos-core.readthedocs.io](https://pebbleos-core.readthedocs.io). ## Developer Certification of Origin (DCO) To make a good faith effort to ensure licensing criteria are met, this project requires the Developer Certificate of Origin (DCO) process to be followed. The DCO is an attestation attached to every contribution made by every developer. In the commit message of the contribution, (described more fully later in this document), the developer simply adds a `Signed-off-by` statement and thereby agrees to the DCO. When a developer submits a patch, it is a commitment that the contributor has the right to submit the patch per the license. The DCO agreement is shown below and at http://developercertificate.org/. ``` Developer's Certificate of Origin 1.1Bymakingacontributiontothisproject,Icertifythat:(a)ThecontributionwascreatedinwholeorinpartbymeandIhavetherighttosubmititundertheopensourcelicenseindicatedinthefile;or(b)Thecontributionisbaseduponpreviousworkthat,tothebestofmyknowledge,iscoveredunderanappropriateopensourcelicenseandIhavetherightunderthatlicensetosubmitthatworkwithmodifications,whethercreatedinwholeorinpartbyme,underthesameopensourcelicense(unlessIampermittedtosubmitunderadifferentlicense),asindicatedinthefile;or(c)Thecontributionwasprovideddirectlytomebysomeotherpersonwhocertified(a),(b)or(c)andIhavenotmodifiedit.(d)Iunderstandandagreethatthisprojectandthecontributionarepublicandthatarecordofthecontribution(includingallpersonalinformationIsubmitwithit,includingmysign-off)ismaintainedindefinitelyandmayberedistributedconsistentwiththisprojectortheopensourcelicense(s)involved. ``` ### DCO Sign-Off The “sign-off” in the DCO is a “Signed-off-by:” line in each commit’s log message. The Signed-off-by: line must be in the following format: ``` Signed-off-by:YourName\ ``` For your commits, replace: - `Your Name` with a known identity (sorry, no anonymous contributions.) - `your.email@example.com` with the same email address you are using to author the commit (CI will fail if there is no match) You can automatically add the Signed-off-by: line to your commit body using `git commit -s`. Use other commits in the repository as examples. Additional requirements: - If you are altering an existing commit created by someone else, you must add your Signed-off-by: line without removing the existing one. ## Commit Requirements - All commits must be atomic and self-contained. Each commit should represent a single logical change to the codebase. Avoid bundling multiple unrelated changes into a single commit. - Each commit message must be in the format of “area: description”, where “area” is a short identifier for the part of the codebase being changed (e.g., “services” or “applib”) and “description” is a brief summary of the change. - Each commit message must include the appropriate DCO sign-off as described above. - If your commit addresses a specific issue, please include a reference to the issue number in the commit message (e.g., “Fixes #123”). - If your commit includes changes to documentation, please ensure that the documentation is updated accordingly and that the commit message reflects this. ## Generative AI usage The use of large language models (LLMs) is acceptable **if and only if** a thorough manual review is performed. **By submitting a pull request, you are guaranteeing the following:** - You have _personally_ reviewed all generated code to make sure that it will not cause undue technical issues, including testing for edge cases, ensuring data integrity, and verifying that performance standards are maintained. - You have ensured that PebbleOS’s UX standards are not meaningfully degraded by this change. - You have ensured that any changes to the user interface are in keeping with the design of the rest of the app, or have provided good reason to change them. - You fully understand the content of the pull request and are prepared to explain it. - You have verified that the change does not reduce overall code quality or maintainability. - You own the rights to all content provided, or the license or attribution from the licensed content is properly included. The following rules additionally apply to contributions made in whole or part using generative AI: - The use of generative AI is clearly disclosed with one or more of the following: - All submitted text is in English. We do not have the capacity to review text or translations for other languages at this time and we do not trust LLMs to handle this correctly. - No AI-generated images, icons, SVG files, or other critically artistic works are included in the content of the pull request. ## Repository conventions - C code is formatted with clang-format, Python with ruff. - Keep code comments short and concise; extended rationale belongs in the Git commit message. Do not reference issue numbers in code, only in commit messages. - To backport a merged pull request to a release branch, add a `backport-` label (e.g. `backport-4.36-branch`) to it, either before or after merging. A workflow cherry-picks the pull request’s commits onto `` and opens a new pull request against it; on conflicts it comments on the original pull request instead. The workflow authenticates as a GitHub App (`BACKPORT_APP_ID` / `BACKPORT_APP_KEY` repository secrets) so that backport pull requests trigger CI, which the default `GITHUB_TOKEN` cannot do. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/contributing.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/contributing.md). --- # Debugging Source: https://developer.repebble.com/pebbleos/docs/development/debugging/ ## GDB Start a debug session with: ``` pbl debug ``` On QEMU boards this attaches GDB to the [emulator](https://developer.repebble.com/pebbleos/docs/development/qemu.md) through a proxy (`tools/qemu/qemu_gdb_proxy.py`) that walks the kernel’s thread list, so `info threads` and per-thread backtraces work. On real hardware, `pbl debug` is available on boards whose runner is OpenOCD (e.g. asterix, configured by `boards/asterix/support/openocd.cfg`); boards flashed via sftool do not support it. ### PebbleOS GDB commands `tools/gdb_scripts/gdb_tintin.py` extends GDB with a `pbl` command set. Load it inside GDB with: ``` (gdb)sourcetools/gdb\_scripts/gdb\_tintin.py(gdb)pbl ``` Highlights (run `pbl` for the full list): - `pbl heap` / `pbl heapstats`: parse and profile the heaps - `pbl sbt`: stack backtrace and stack usage statistics - `pbl layer-tree`: dump the UI layer hierarchy - `pbl app_symbols` / `pbl worker_symbols`: load symbols for the currently running app or worker, so app crashes can be symbolicated - `pbl fault_wizard`: after a hard fault, restore the faulting `$sp`/`$lr`/ `$pc` so `bt` shows the crashing stack - `pbl reboot_reason`: decode the reboot reason from the RTC registers - `pbl log-buffer`: dump log messages still buffered in RAM - `pbl to_png`: render the framebuffer or a `GBitmap` to a PNG file ## Console and logs Attach to the firmware console with `pbl console` (add `--tty` for real hardware; see [QEMU](https://developer.repebble.com/pebbleos/docs/development/qemu.md) for the emulator ports). Firmware log messages are hashed at compile time — the binary only contains a hash and the arguments — and the console dehashes them on the fly using the dictionary generated by the build at `build/fw/loghash_dict.json` (`build/prf/fw/loghash_dict.json` for PRF builds). The dehashing code lives in `tools/log_hashing/` and `tools/libs/pebble-loghash/`. Logs are also persisted to a circular buffer in flash; `tools/dehash_flash_logs.py` parses and dehashes a dump of that region. ## Coredumps When the firmware crashes, it writes a coredump to SPI flash (see the [architecture overview](https://developer.repebble.com/pebbleos/docs/architecture.md) for the on-flash format). - `tools/analyze_coredump.py ` runs GDB in batch mode and prints a full report: backtraces for all threads, registers, heap and lock statistics, and build metadata. You can also open a coredump interactively with `arm-none-eabi-gdb-py -ex "core-file "` and use the `pbl` commands above. ## Memory usage analysis `tools/` contains several ELF analyzers for RAM and flash footprint work, e.g. `analyze_fw_static_memory_usage.py` and `analyze_mcu_flash_usage_treemap.py` (renders an interactive treemap of flash usage). Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/debugging.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/debugging.md). --- # Exposing functions to the SDK Source: https://developer.repebble.com/pebbleos/docs/development/sdk_export/ Third-party apps do not link against the firmware directly: the app SDK is generated from the firmware sources at build time. When you expose a new function to apps (i.e. anything declared in an `applib/` header that user apps can call), three things must change together — the firmware build alone will not surface it to apps: 1. **Implement the applib wrapper and syscall** — add the function to the appropriate `fw/applib/.../.c/.h`, declare the `sys_*` syscall in `fw/syscall/syscall.h`, and define it with `DEFINE_SYSCALL` (from `fw/syscall/syscall_internal.h`), either in the matching `fw/syscall/_syscalls.c` or alongside the implementation it wraps. 2. **Register the symbol** in `tools/generate_native_sdk/exported_symbols.json` under the matching group, with an `addedRevision` matching the new SDK revision. 3. **Bump the SDK revision** in `fw/process_management/pebble_process_info.h`: increment `PROCESS_INFO_CURRENT_SDK_VERSION_MINOR` and add a comment line above the `#define` following the existing pattern, e.g. `// sdk.major:0x5 .minor:0x66 -- (rev 105)`. The `rev` number in the comment must match `addedRevision` from step 2. Forgetting steps 2 or 3 means the function compiles into the firmware but is invisible to the app SDK build, so third-party apps can’t link against it. Warning It is **not** possible to add publicly exposed functions to an already released firmware/SDK combination. The generated `pebble.auto.c` function-pointer table must be compiled into the firmware the SDK targets: an app built against a newer SDK calls a trampoline that indexes past the end of an older firmware’s table and crashes. New exports always ship as a new firmware plus a new SDK build. ## How the SDK generator works The generator (`tools/generate_native_sdk/generate_pebble_native_sdk_files.py`) runs automatically as part of the firmware build (normal variant only — PRF and test builds skip it). It exports the white-listed functions, `typedef`s and `#define`s from the firmware tree and produces the files needed to build native watchapps, all under `build/`: - `build/sdk//include/pebble.h` — typedefs, defines and function prototypes for apps (plus `pebble_worker.h` for background workers and a few version/fonts headers). Exported declarations are copied verbatim, so the SDK also ships `pbl/kernel/compiler.h` and its backends, included from `pebble.h`, and exported headers use its `PBL_*` macros rather than raw ` __attribute__ `. - `build/sdk//lib/libpebble.a` — static library containing trampolines that call the exported functions in flash - `build/fw/pebble.auto.c` — `g_pbl_system_tbl`, the table of function pointers the trampolines use to find an exported function’s address; compiled into the firmware image The rest of the distribution is packaged by the firmware build’s `sdk` target, which fills in the same `build/sdk/` tree: ``` pbl configure --board $BOARDpbl build sdk ``` `tools/cmake/sdk.py` copies the common files from `sdk/` into `build/sdk/common/` — the app project templates under `sdk/defaults/`, and the tools and resource pipeline the app build shares with the firmware — and bundles `sdk/waftools/` into the `waf` binary app developers use to build their apps. That waf is the only one left in the tree: everything it needs lives under `sdk/`, and it is built out of tree, so packaging leaves the checkout untouched. ## `exported_symbols.json` format ``` {"revision": "\","version": "x.x","files": ["\"],"exports": ["\"]} ``` Each exported symbol has a `type` of `function`, `define`, `type`, `forward_struct`, or `group`: ``` {"type": "function","name": "\","sortName": "\","addedRevision": "\"} ``` A `group` nests further `exports` under a `name`. Functions support additional flags (`internal`, `removed`, `deprecated`, `appOnly`, `workerOnly`, `implName`, `skipDefinition`) — see existing entries and `tools/generate_native_sdk/exports.py` for their meaning. Notes: - Functions are sorted by `addedRevision`, then alphabetically (by `sortName` if present, else `name`) within a revision. This ordering is the ABI — it is why new functions must use a new revision: it guarantees new firmware stays backwards compatible with apps compiled against an older `libpebble.a`. - `types` are emitted in the order listed; put typedefs after the typedefs they depend on (`includeAfter` is the escape hatch for ordering exceptions). - The generator errors out on exports it cannot find in the parsed headers and on inconsistent revision numbers, but it does not verify that the resulting `pebble.h` compiles — review its output. - The comment ledger above `PROCESS_INFO_CURRENT_SDK_VERSION_MINOR` is the only mapping between SDK revisions and version minors: no formula relates them (the minor once jumped `0x19` → `0x20` between revs 35 and 36, and a few minors and revs are skipped or doubled up). Treat the ledger as append-only history. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/sdk\_export.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/sdk_export.md). --- # Integration tests Source: https://developer.repebble.com/pebbleos/docs/development/integration_tests/ Integration tests run the firmware as it ships, on the emulator or on a real watch, and drive it from the outside: pressing buttons, comparing screenshots, reading logs and measuring current. They are written in Python with [pytest](https://docs.pytest.org) and live in `tests/integration`, next to the harness that runs them (`tests/integration/harness`). Unit tests, which build parts of the firmware for the host, are described in [Running and writing tests](https://developer.repebble.com/pebbleos/docs/development/testing.md). ## Running `pbl itest` runs the suite against the configured build. On an emulated board it builds the flash images, boots QEMU headless on a private copy of them, and shuts it down when the tests are done: ``` pbl configure --board qemu_emery pbl build pbl itest ``` A native board (see [Native](https://developer.repebble.com/pebbleos/docs/development/native.md)) runs as a host process instead, on a fresh flash file, serving its console and the QEMU serial protocol on TCP ports. Built with sanitizers, it runs the suite under them: ``` pbl configure --board native_emery -DCONFIG_ASAN=y -DCONFIG_UBSAN=y pbl itest ``` The process’s output, sanitizer reports included, is in `native.log` with the rest of the results. Anything `pbl itest` does not recognize goes straight to pytest, which it runs from `tests/integration`, so the usual selection options work: - `bluetooth`, `--ignore=bluetooth`: by area - `-k settings`: by name - `ui/test_navigation.py::test_settings`: a single file or test - `--integration`: what CI runs, see below - `--collect-only -q`: list the tests without running them - `-n 4`: run on four emulators or native watches at once, one per pytest-xdist worker; most of a test’s time is spent waiting on the watch, so this scales past the CPU count. Not on a real watch. `pbl itest` is only a convenience; the same run is ``` cd tests/integration pytest --build-dir=../../build ``` When calling pytest directly, pass paths as `--option=value`: pytest reads a bare path argument (a build directory, a tty) as a test path before it has loaded the harness’s options. `pbl itest` loads the harness up front and has no such limitation. ### On a real watch Give the debug console’s serial port, and optionally have the harness flash the build first (with `pbl flash --resources`, or `--flash-command`): ``` pbl -b build-obelix itest --device-serial /dev/tty.usbserial-1 \--ppk2 auto --flash-before ``` The device type follows the board (`qemu` for emulated boards, `hardware` otherwise), and can be forced with `--device-type`. Flash with a PPK2 powering the watch (see [Current measurement](#current-measurement)): the harness then repowers the watch and flashes it right away, well within a second, before the firmware can deep sleep, which leaves its debug UART reachable only at random. `--erase-fs` erases the watch’s filesystem first (bondings, settings, apps and data) and the bonding kept for PRF, for a known starting point; it needs a board flashed with sftool. The integration tests run debug builds, including the current measurements (see below): release builds have no usable console. ### Setups and lab files The tests run with a setup: the device, how it is reached and powered, and what plays the phone. The same tests run on any of: | Watch | Phone | How | | --- | --- | --- | | Emulator or native | Bumble, software controllers | a `CONFIG_BT_HCI_UART` build, by default | | Emulator or native | Bumble, on a dongle | `--bt-hci lab`: the lab’s first dongle for the watch, its second for the phone | | Watch, serial console | Bumble, on a dongle | the lab’s watch, and its first dongle for the phone | | Watch, serial console | CoreApp | `--phone coreapp` (not supported yet) | A lab file describes the hardware wired to this host, and nothing else: watches (board, serial port, the supply powering them), power supplies and Bluetooth dongles. Pass it with `--lab` (or `$PBL_ITEST_LAB`), and the harness takes the setup for the build from it: the watch running its board (`--lab-watch` picks another), its supply, and a dongle for the phone. Options on the command line override it. `tests/integration/lab.example.yaml` describes the format: ``` pbl -b build-getafix itest --lab ~/pebble-lab.yaml --flash-before ``` The report starts with the setup, e.g. `setup: getafix, serial /dev/tty.wchusbserial1, PPK2 auto at 3800 mV, phone bumble on /dev/cu.usbmodem1101`. Tests that need what the setup lacks are skipped with the reason, without touching the device: a test needs a phone when it takes `phones`, and a power supply when it takes `power`. ### Connections The harness reaches the watch through one or more connections, each offering some of: the debug prompt, the log stream, and the Pebble protocol (what the phone app speaks, used for input, screenshots and blob DB). Each is served by the first connection that has it. By default the harness uses the debug console, over PULSE when `CONFIG_PULSE_EVERYWHERE` is set and as the legacy text console otherwise; `--connection` replaces the default and may be repeated: | Connection | Prompt | Logs | Pebble protocol | | --- | --- | --- | --- | | `pulse:TTY` / `pulse:socket://HOST:PORT` | yes | yes | yes (tunneled) | | `serial:TTY[@BAUD]` | yes | yes | no | | `ble:WATCH[@CONTROLLER]` | no | no | yes | | `devconn:HOST[:PORT]` | no | app only | yes | | `qemu:HOST:PORT` | no | no | yes | For example, a watch over Bluetooth through the phone app’s developer connection, with logs and the prompt over serial: ``` pbl itest --connection serial:/dev/tty.usbserial-1 --connection devconn:192.168.1.20 ``` Tests that need something no connection offers are skipped, e.g. the `prompt` fixture without a prompt. ### Bluetooth Bluetooth goes through nRF52840 dongles running Zephyr’s `hci_uart` sample, an H4 controller on the dongle’s USB serial port; see [QEMU](https://developer.repebble.com/pebbleos/docs/development/qemu.md) for building and flashing it. On macOS use the `cu.*` device. `ble:` connects to the watch as the phone app would: it pairs (or encrypts with the bond it keeps, in `~/.cache/pbl-itest`), then speaks the Pebble protocol over reversed PPoGATT, through a controller [Bumble](https://google.github.io/bumble/) drives. `WATCH` is the watch’s address, its advertised name, or `auto` for the first watch advertising; the controller is a dongle’s serial port, or `--ble-controller`: ``` pbl itest --connection ble:auto@/dev/cu.usbmodem1101 \--connection pulse:/dev/tty.usbserial-1 ``` Pairing has to be confirmed on the watch; the harness does it with the prompt when another connection offers one, so list the serial connection too. A phone bonded to the watch reconnects to it before the harness can: turn its Bluetooth off. The harness keeps the connection parameters it opens the link with and declines the watch’s requests to change them: those updates stall the watch’s sending for seconds, and some fail and drop the link. An emulator or a native build with `CONFIG_BT_HCI_UART` needs a controller of its own (QEMU’s fourth serial port, the native program’s `-b`). By default it gets Bumble’s software controllers (`virtual`), two linked in memory, one for the watch and one for the harness: they cover the host stacks and the protocols above them, not a radio, and are what CI uses: it runs the normal tests on a normal build and the PRF tests on a PRF build, both with `CONFIG_BT_HCI_UART`, so that tests needing a phone run too. With real ones it takes two dongles, one for the watch and one for the harness: the lab’s (`--bt-hci lab`), or given on the command line: ``` pbl configure --board qemu_emery -DCONFIG_BT_HCI_UART=y pbl itest --bt-hci /dev/cu.usbmodem1101 --ble-controller /dev/cu.usbmodem1201 ``` Tests that need a phone take the `phones` fixture: `phones()` makes the setup’s phone, and `connect()` pairs and opens the Pebble protocol session (`phone.pebble`), confirming the pairing on the watch. `pair()` connects too, but leaves the pairing to the test: `number()` is the code the phone shows, `answer()` the phone’s answer and `result()` whether it paired, while `harness.helpers.pairing` reads the watch’s prompt (`bt pairing`) and answers it. A Bumble phone can also be another phone to the watch, `phones(address=...)`, with a bond of its own, and host the PPoGATT service itself, `phones(ppogatt="forward")`, instead of using the one the watch hosts; tests that ask for these skip on other phones. It can also ask for another ATT MTU (`mtu=23`), grant the watch’s connection parameter updates (`accept_parameters=True`), and serve another PPoGATT meta characteristic (`forward_meta=...`); its `link` reads the watch’s GATT characteristics, resets the PPoGATT session and records the parameters the watch asks for. `harness.helpers.firmware` installs a firmware bundle through a phone, as the phone app does: ``` def test\_version(phones):phone=phones().connect()assertphone.watch\_version().version\_tag ``` `phone.inbox` keeps every message the watch sends the phone, from the start of the session; `mark()` and `wait(endpoint, match, timeout, since)` work as the log’s do, and `phone.send(endpoint, payload)` sends raw payloads. `harness.helpers.blobdb` writes blob DB records and timeline items as the phone app does. The `bluetooth/test_*_endpoints.py` tests cover the Pebble protocol endpoints the watch serves the phone this way. ### Results Everything a run produces goes to `BUILD/itest` (or `--results-dir`): - `junit.xml`: the report - `device.log`: the dehashed log of the whole session, with a header at the start of each test - `/device.log`: each test’s slice of it - `/*.actual.png`, `*.diff.png`: screenshots and, on a mismatch, the differences - `/failure.png`: the screen when a test failed; the tail of the log is added to the failure report too - `/.json`, `.csv`: current measurements - `qemu.log`, `uart1.log`, `flash.log`: the emulator’s and the flasher’s output - with `-n`, the session-wide files are in a directory per worker (`gw0`, `gw1`, …) ## Selecting tests by device Tests declare where they apply with markers, and those that do not apply to the build under test are deselected: - `boards("obelix", "qemu_emery")`: the board, without revision - `platforms("emery")`: the platform, covering its emulated board too - `device_types("hardware")`: `qemu` or `hardware` - `requires_config("CONFIG_TOUCH")`: Kconfig symbols that must be set - `variants("prf")`: the firmware variant, `normal` or `prf`; unmarked tests are for `normal` The names are also keywords, so `-k obelix` selects tests declared for obelix. `--board` selects for another board than the build’s, e.g. to see what would run on one without a build for it: ``` pbl itest --collect-only -q --board obelix --device-type hardware ``` CI runs with `--integration`, which also honours `integration_boards`: a test marked `integration_boards("qemu_emery", "getafix@dvt2")` runs anywhere it applies, but CI runs it only on qemu\_emery and on the lab’s getafix@dvt2 (a board, or a board at a revision); `integration_boards()` keeps it out of CI. Use it for tests that do not depend on the board, or that take too long for every pull request. The lab’s watches run the suite nightly, normal and recovery firmware. A maintainer can run it on a pull request by adding the `ci:hw-itest` label; each time the label is added, the pull request runs once more. The run executes the pull request’s own code on the lab machine, so review all of it first, including tooling and Python requirements. On someone else’s pull request, approve the head commit before adding the label: a label from anyone other than the author is refused unless they approved the commit that would run. The markers are in `harness/plugin.py`; they are strict, so a new one must be added there. ## Writing a test Tests are grouped by area in subdirectories of `tests/integration` (`system`, `ui`, `notifications`, `voice`, `power`). A test asks for the fixtures it needs: ``` from harness.helpers.ui importButtondef test\_settings(ui,snapshot):ui.press(Button.SELECT)ui.wait\_idle()ui.press(Button.SELECT)image=ui.wait\_idle()assertui.top\_window()=="Settings"snapshot.assert\_match(image,"settings") ``` The fixtures: - `dut`: the launched device. `dut.prompt(cmd)` runs a prompt command, `dut.protocol` is a libpebble2 connection, `dut.wait_for_log(regex, since=dut.logs.mark())` waits for a log line, and `dut.reset()` restarts the firmware. - `ui`: input and screen helpers, starting from the watchface: `press`, `long_press`, `hold`, `swipe`, `tap` (QEMU only), `screenshot`, `wait_idle` (until the screen stops changing), `window_stack`, `modal_stack`, `top_window`, `launch_app`, `set_time` and `go_home`. - `prompt`: `dut.prompt`, skipping the test when there is no prompt. The commands the harness itself sends are spelled for the build’s console, the shell or the older prompt (`harness/commands.py`); `dut.command()` gives them to tests. - `snapshot`: screenshot comparison, below. - `power`: current measurement, below. - `build`: the build under test (`board`, `platform`, `config`). `harness.helpers.notifications` inserts notifications as the phone app would, and `harness.helpers.voice.VoicePhone` answers dictation sessions (`voice start` on the shell) as the phone app would, recording the audio frames it receives. The launched device is shared by the whole session by default; for a fresh boot per test (or module) pass `--dut-scope function` (or `module`). The emulator’s clock starts at a fixed time (`--qemu-rtc`) so that screens are reproducible. ## Screenshot comparison `snapshot.assert_match(image, name)` compares a screenshot with `golden///.png`. Screenshots are the framebuffer as the firmware renders it, taken over the Pebble protocol when a connection carries it and from the emulator’s display otherwise. To create or update golden images, run the tests with `--update-golden` and review the new images before committing them: ``` pbl itest --update-golden -k settings ``` Golden images are per board, and a real watch renders what its own settings say: e.g. the status bar clock follows its timezone and 12/24h preference. Parts of the screen that legitimately change can be left out with `mask`, a list of `harness.helpers.snapshot.Region(x, y, w, h)`; `tolerance` allows small per-channel differences and `max_diff_pixels` a number of differing pixels. ## Current measurement Power tests use a Nordic Power Profiler Kit II as a source meter: it replaces the battery and supplies VBAT, so the harness can also power the watch on and off. Connect the PPK2’s VOUT and GND to the battery terminals, and pass its port and the voltage to supply. A PPK2 shows up as two serial ports of which only one answers; `auto` finds it: ``` pbl -b build-obelix itest --device-serial /dev/tty.usbserial-1 \--ppk2 auto --ppk2-voltage 3800 power ``` With a PPK2 the harness powers the watch on before the session, and `dut.reset()` power-cycles it. `power.measure_idle(name)` measures the watch as if it were unplugged: the console stops listening (`sys rx_disable`), which lets the firmware sleep as a release build would, and the harness drops its connections for the duration. The watch is left to settle, then measured for 60 s, then the harness reconnects: ``` def test\_idle(ui,power):ui.go\_home()m=power.measure\_idle("idle")assertm.mean\_ua\.json` (the summary) and `.csv` (1 ms averages); record them with `record_property` to have them in the JUnit report. A measurement that lost samples fails rather than report a wrong figure. The power tests measure TicToc idling while advertising fast and slow for discovery, and in airplane mode. They wipe the watch first (`dut.wipe()`), so run them on a test watch, with no phone bonded to it nearby. Each passes within 10% of the board’s nominal figure in `power/test_idle.py`, measured at 3.8 V; update the nominals when a change moves them on purpose. Boards without nominals record their figures with a warning. ## Recovery firmware The tests in `bluetooth/` run on both firmwares: pairing, with the phone’s name and the code on the watch’s prompt, confirmed or declined on either side or left to time out, and the Pebble protocol over either PPoGATT. The tests in `prf/` cover what else a PRF release is checked for: the Getting Started screen and the phone’s name on it, a second phone taking over the single bond, installing the normal firmware from the phone and “Reset to PRF” from it, the backlight timeout, turning off after 10 minutes unplugged and unconnected (not while a phone or a charger is connected), the low battery screen, and the current advertising, connected and off. On the emulator the screens are compared with golden images, the backlight is timed on the display, and `dut.set_battery()` sets the emulated battery’s charge and charger; on a watch the current is measured with a PPK2, the backlight timed on it, and the low battery screen shown by powering it at 3.5 V. Installing the firmware takes a normal build of the same board, bundled: ``` pbl -b build-main configure --board qemu_emery pbl -b build-main build bundle pbl -b build-prf configure --board qemu_emery --variant prf -DCONFIG_BT_HCI_UART=y pbl -b build-prf build qemu_image_micro qemu_image_spi pbl -b build-prf itest --no-build --bt-hci virtual --main-build "$PWD/build-main" ``` The emulator has no bootloader, so it only checks the transfer. A watch installs it, boots it, and goes back to PRF when the phone asks; it has to be running PRF to start with, as set up for a release check (the bootloader and PRF alone on the flash). The idle shutdown tests take about 12 minutes each, so CI does not run them. Left for a person: the Back+Up+Select hold that reboots into PRF, charging a watch, the MFG menu’s tests (checked in the factory), and the phone apps themselves. ## Extending the harness - A new connection backend is a `Connection` subclass in a module of `harness/connections`, with its `scheme` and `capabilities`; it is picked up by `--connection SCHEME:ADDRESS`. - A new kind of device is a `DeviceAdapter` subclass in `harness/device`, registered in `harness/device/factory.py`. - Helpers built on the device go in `harness/helpers`, with a fixture in `harness/fixtures.py`. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/integration\_tests.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/integration_tests.md). --- # Moddable JS Engine Source: https://developer.repebble.com/pebbleos/docs/development/moddable/ PebbleOS supports the [Moddable SDK](https://github.com/coredevices/moddable)’s XS JavaScript engine. The Moddable integration provides a lightweight sandbox for running JavaScript applications and watchfaces on Pebble hardware. ## Building with Moddable Moddable is enabled by default on platforms whose board defconfig sets `CONFIG_MODDABLE_XS=y`. You can override this at configure time by passing `-DCONFIG_MODDABLE_XS=y` or `-DCONFIG_MODDABLE_XS=n` to `pbl configure`. PRF (recovery) builds always disable the engine. ## Installable applications The JavaScript is stored in a resource. A small native C application bootstraps JavaScript execution by calling `moddable_createMachine`. The JavaScript itself is a Moddable SDK **mod** , which is a precompiled collection of modules. The modules are compiled using the `mcrun` tool in the Moddable SDK. See Moddable’s [Pebble Examples repository](https://github.com/moddable-OpenSource/pebble-examples) for further details. The Pebble resource containing the precompiled mod is currently loaded into RAM for execution; eventually, it should be executed directly from flash (this is what the Moddable SDK does on most platforms). ## Sandbox The JavaScript in installable applications runs inside a lightweight sandbox, which can limit access to certain JavaScript global variables and modules. Installable applications can be either a normal application or a watchface. The sandbox prevents watchfaces from subscribing to the Pebble hardware buttons (which is slightly redundant since PebbleOS also blocks them — but PebbleOS blocks them silently, whereas the sandbox throws an exception, which is more clear to developers). Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/moddable.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/moddable.md). --- # Native Source: https://developer.repebble.com/pebbleos/docs/development/native/ The `native_emery` board builds PebbleOS as an application for the machine you are working on (Linux or macOS), with a window standing in for the display. It boots much faster than QEMU and runs under the host’s debugger, sanitizers and profilers. Important The native port is a sketch. Drivers are emulated or stubbed, Bluetooth uses the fake HCI controller, and third-party apps cannot run: they are ARM binaries. Only macOS has been tried so far. ## Requirements A host C compiler and [SDL2](https://www.libsdl.org), both part of the dependencies in [Prerequisites](https://developer.repebble.com/pebbleos/docs/development/getting_started.md). ## Build and run ``` pbl configure --board=native_emery pbl build build/pebbleos ``` | Key | Button | | --- | --- | | Up | Up | | Down | Down | | Right, Enter, Space | Select | | Left, Backspace, Escape | Back | The mouse drives the touchscreen: click and drag, or scroll with the wheel or two fingers on a trackpad, which drags a finger from the pointer. The terminal is the watch’s serial console: logs go there, and as on the watch, `Ctrl-C` opens the shell prompt and `Ctrl-D` leaves it. `Ctrl-\` quits. Options: - `-f FILE`: file backing the external flash, `pebbleos-flash.bin` in the current directory by default. It keeps the watch’s state across runs. - `-r FILE`: system resources installed at boot, the ones from the build by default. - `-s N`: window scale factor. - `-t SECONDS`: quit after that long. - `-c PORT`: serve the console on a TCP port, one client at a time, instead of the terminal. Its output still goes to stdout. - `-p PORT`: serve the QEMU serial protocol on a TCP port, where a phone connects with libpebble2’s QEMU transport (`pebble --qemu`). - `-b PORT|DEVICE`: with `CONFIG_BT_HCI_UART`, connect the Bluetooth HCI UART to an H4 controller: a TCP port on the local host (e.g. Bumble’s `tcp-server` transport) or a serial device, such as an nRF52840 dongle running Zephyr’s `hci_uart` (see [QEMU](https://developer.repebble.com/pebbleos/docs/development/qemu.md)). - `-S FILE`: save the last frame to a BMP file on quitting. A reset of the watch restarts the process. A fatal error aborts it, so that a debugger stops right there: ``` lldb build/pebbleos ``` ## Sanitizers The compiler’s sanitizers can be built in with Kconfig options: ``` pbl configure --board=native_emery -DCONFIG_ASAN=y -DCONFIG_UBSAN=y ``` - `CONFIG_ASAN`: AddressSanitizer, out-of-bounds and use-after-free accesses. The firmware heaps keep what they have not handed out poisoned. `CONFIG_ASAN_RECOVER` reports errors and goes on, with `ASAN_OPTIONS=halt_on_error=0`. - `CONFIG_UBSAN`: UndefinedBehaviorSanitizer, without the alignment check. - `CONFIG_TSAN`: ThreadSanitizer, not together with AddressSanitizer. Firmware threads take turns on one CPU, so it mostly checks the host side. ## How it works The kernel’s `posix` architecture (`kernel/arch/posix`) runs each kernel thread as a pthread. Only the thread holding the CPU lock runs, and switches hand the lock over explicitly, so scheduling stays the kernel’s. Interrupts come from host threads: the 1 kHz tick, the window’s input and the terminal. A host thread takes the CPU lock between kernel threads, runs the handler as an ISR and releases it, much like an interrupt arriving between instructions. Kernel threads are not preempted while they hold the CPU, so an interrupt waits for the running thread to block. Drivers come in two halves, as in Zephyr’s `native_sim`: the top half is firmware code, and the bottom half (`*_bottom.c`, listed with `pbl_host_sources()`) talks to the host and sees none of the firmware headers. The `posix` SoC (`soc/posix`) owns the process `main()`, which knows no driver: it starts the firmware on a thread of its own and runs the main thread hooks and command line options that bottoms register (`posix_host.h`). SDL is one such bottom (`soc/posix/sdl`), selected by the drivers that use it: the display (`DISPLAY_SDL`), the buttons (`BUTTON_SDL`, the keyboard) and the touchscreen (`TOUCH_SDL`, the mouse). It runs on the main thread, as macOS requires. A native build is linked without the firmware linker script (`PBL_NO_LINKER_SCRIPT`, decided in `cmake/modules/linker.cmake`). Code that gathers objects from many files, like the shell commands, then uses `PBL_UNSORTED_SECTION()`: the host linker collects the section on its own but cannot sort it, so the shell picks commands by name when looking them up. Things the linker script provides on the target, such as the heaps, the app RAM and the build ID, are plain objects in `soc/posix/memory.c`. Firmware `malloc()` and `free()` are renamed to the firmware heaps at compile time, as `--wrap` does on the target. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/native.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/native.md). --- # Prerequisites Source: https://developer.repebble.com/pebbleos/docs/development/getting_started/ Follow this guide to: - Set up a command-line PebbleOS development environment - Get the source code ## PebbleOS SDK Install the [PebbleOS SDK](https://github.com/coredevices/PebbleOS-SDK), which bundles the ARM GNU toolchain, Pebble QEMU, and other tools: ``` curl -LsSf https://github.com/coredevices/PebbleOS-SDK/releases/latest/download/pebbleos-sdk-installer.sh | sh ``` The build locates the SDK on its own when a build directory is configured: it honours `PEBBLEOS_SDK_ROOT` if set (the SDK’s `env.sh` exports it), and otherwise picks the newest `pebbleos-sdk-` under your home directory or `/opt` that satisfies the version in `SDK_VERSION`. The SDK root and the paths of the tools found in it (toolchain, QEMU, sftool, gdb) are cached in the build directory, so a build keeps using the SDK it was configured with. To use a specific install, pass `-DPEBBLEOS_SDK_ROOT=` to `pbl configure`. ## System-level dependencies A series of system-level dependencies are required. Follow the next steps to install them. Ubuntu 24.04 LTS 1. Update first: ``` sudo apt update ``` 1. Install required dependencies ``` sudo apt install \bison \clang \flex \gcc \gcc-multilib \gettext \git \gperf \libfreetype6-dev \libglib2.0-dev \libgtk-3-dev \libncurses-dev \librsvg2-bin \libsdl2-dev \make \nodejs \openocd \pkg-config \python3-dev \python3-venv ``` Fedora 44 1. Upgrade first: ``` sudo dnf upgrade --refresh ``` 1. Install required dependencies ``` sudo dnf install \bison \clang \dash \flex \freetype-devel \gcc \gettext \git \glib2-devel \gperf \gtk3-devel \librsvg2-tools \make \ncurses-devel \nodejs \openocd \pkgconf-pkg-config \python3-devel \SDL2-devel ``` macOS 1. Install [brew](https://brew.sh/). 2. Install dependencies: ``` brew install python openocd $(cat requirements-brew.txt) ``` 1. Link `brew` Python: ``` brew link python@3 ``` ## Get the source code You can clone the PebbleOS repository by running: ``` git clone --recurse-submodules https://github.com/coredevices/pebbleos ``` Once cloned, enter the `pebbleos` directory before continuing: ``` cd pebbleos ``` ## Python dependencies A series of additional Python dependencies are also required. Follow the next steps to install them in a [Python virtual environment](https://docs.python.org/3/library/venv.html). 1. Create a new virtual environment: ``` python3 -m venv .venv ``` 1. Activate the virtual environment: ``` source .venv/bin/activate ``` Tip Remember to activate the virtual environment before every time you start working! 1. Install dependencies ``` pip install -r requirements.txt ``` This also installs `pbl`, the developer CLI you drive the build with. With the virtual environment active it is on the `PATH` from anywhere inside the checkout: ``` pbl configure --board asterix pbl build ``` See [The pbl CLI](https://developer.repebble.com/pebbleos/docs/development/pbl.md) for what it can do and how it is put together. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/getting\_started.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/getting_started.md). --- # QEMU Source: https://developer.repebble.com/pebbleos/docs/development/qemu/ Important Some platforms have dedicated QEMU board targets: `qemu_emery`, `qemu_flint`, and `qemu_gabbro`. ## Getting QEMU The QEMU binary ships with the [PebbleOS SDK](https://github.com/coredevices/PebbleOS-SDK). ## Build The steps here are similar that of real hardware: ``` pbl configure --board=$BOARDpbl build ``` where `$BOARD` is one of the dedicated QEMU boards (`qemu_emery`, `qemu_flint`, `qemu_gabbro`). Each targets a specific platform, e.g. `qemu_emery` targets the Emery platform used by Pebble Time 2. ## Run You can launch QEMU with the built image using: ``` pbl qemu ``` The flash image is rebuilt by default on every launch. To keep the existing flash image (e.g. to preserve stored apps), pass `--keep-flash-image`: ``` pbl qemu --keep-flash-image ``` The launched QEMU exposes: - An interactive QEMU monitor on the launching terminal (`-monitor stdio`) - A programmatic socket monitor (`-monitor unix:build/qemu-mon.sock`) - The serial console over TCP on `localhost:12345` (console) and `localhost:12344` (pebble-tool) UART1 output is also captured to `uart1.log` in the repository root. ## Bluetooth By default the NimBLE host runs against a fake controller that accepts every command but never forms a link. To use a real radio, attach an H4 HCI controller to the emulator’s fourth UART (QEMU pebble19 or later). An nRF52840 Dongle running Zephyr’s `hci_uart` sample shows up as a USB serial port and works out of the box: ``` west build -b nrf52840dongle/nrf52840 zephyr/samples/bluetooth/hci_uart nrfutil pkg generate --hw-version 52 --sd-req=0x00 \--application build/zephyr/zephyr.hex --application-version 1 hci_uart.zip# press the dongle's reset button to enter the bootloadernrfutil dfu usb-serial -pkg hci_uart.zip -p /dev/cu.usbmodemXXXX ``` Then build with the UART transport and pass the controller’s port: ``` pbl configure --board qemu_flint -DCONFIG_BT_HCI_UART=y pbl build pbl qemu --bt-hci /dev/cu.usbmodemXXXX ``` `--bt-hci` takes any QEMU `-serial` spec, e.g. `tcp:localhost:9000`. On macOS use the `cu.*` device: opening `tty.*` blocks until carrier detect. The watch generates its identity address on first boot and keeps it in flash, so pass `--keep-flash-image` to keep existing bondings across launches. A fresh flash image gets a new address. ## Console You can launch a console using: ``` pbl console ``` It connects to the running QEMU over the TCP serial port and provides a prompt for sending commands and receiving responses. ## Screenshots With QEMU running, you can capture the display via the socket monitor: ``` pbl screenshot# defaults to build/screenshot.pngpbl screenshot --output /tmp/foo.png ``` Useful to validate or iterate on UI changes. ## Interaction Keyboard input is captured by the QEMU window, so you can interact with the PebbleOS UI directly. Keys can also be sent programmatically over the socket monitor using the `sendkey` command. The key mapping is: | QEMU key | PebbleOS key | | --- | --- | | `left` | `back` | | `right` | `select` | | `up` | `up` | | `down` | `down` | ## Touch On touch-capable boards you can inject touch events into a running QEMU. Coordinates are given in screen pixels; the display size is read from the emulated `pebble-touch` device and scaled automatically. ``` pbl touch 130 130# tap at (130, 130)pbl swipe 130 220 130 40# swipe up (finger bottom -\> top)pbl swipe 130 220 130 40 --steps 20 --duration 0.4 ``` Requires QEMU to be running; `pbl qemu` exposes the QMP socket used for injection. A tap is a finger down then up; a swipe streams intermediate moves so that drag gestures are seen as continuous. Injection uses the absolute-pointer input path; multi-touch is not wired up in the device. ## Feeding phone data Features that depend on the phone app can be exercised without one: `pbl feed` writes into the running emulator what the phone would, over the Pebble protocol serial port. Each kind of data is a subcommand: ``` pbl feed weather# a forecast for a few built-in citiespbl feed weather Tokyo Sydney --seed 3# a subset; the first is the current locationpbl feed weather --clear# remove every locationpbl feed music# a player and playlist; keeps serving until Ctrl-Cpbl feed music --title "Demo" --paused# a single track of your ownpbl feed calendar# a day of events around nowpbl feed calendar --title "Dentist" --start 15 --location "Downtown"pbl feed calendar --clear# remove every pinpbl feed notifications# a few messages from two senderspbl feed notifications --sender Anna --body "Lunch?" --app WhatsApp pbl feed notifications --clear# remove every notification ``` `pbl feed --help` lists the feeds and `pbl feed --help` their options. A feed that writes a database, like weather, exits once the data is stored. One that stands in for the phone, like music, keeps running: it acts on the watch’s controls, advances playback and draws album art when asked (album art is off by default, under Settings \> Music). The emulator’s port serves one host client at a time, so run one such feed at once. The feeds live in `tools/libs/pbl-cli/pbl/feeds/`, one module per kind of data; see [the `pbl` CLI](https://developer.repebble.com/pebbleos/docs/development/pbl.md) for how to add one. ## Debug You can debug with GDB using: ``` pbl debug ``` ## Install .pbw applications You can install applications coming from .pbw files onto QEMU. This requires [pebble-tool](https://github.com/coredevices/pebble-tool) to be installed and having `pebble` available in the $PATH. Start by launching QEMU as you normally would: ``` pbl qemu ``` Inside another shell locate a .pbw file to install: ``` pebble install /path/to/your/file.pbw --qemu ``` The `pebble` CLI will detect the running QEMU instance and install the application. It should start automatically. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/qemu.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/qemu.md). --- # Running and writing tests Source: https://developer.repebble.com/pebbleos/docs/development/testing/ Unit tests live under `tests/` and run on the host (not on device or QEMU; for those, see [Integration tests](https://developer.repebble.com/pebbleos/docs/development/integration_tests.md)), using a vendored copy of the [clar](https://github.com/clar-test/clar) test framework in `tools/clar/`. Code under test is compiled for the host together with fakes and stubs that replace hardware and OS dependencies. ## Running tests The tests are a CMake project of their own, separate from the firmware build because they build for the host rather than for the watch. `pbl test` configures `build-test/`, builds every test and runs them under ctest: ``` pbl test ``` Anything `pbl test` does not recognise is passed straight to ctest, so its selection and reporting options are all available: - `-R REGEX`: run the tests whose name matches, e.g. `pbl test -R animation` - `-L LABEL`: run by label; every test is labelled with its directory (`tests/fw/ui`) and its platform (`obelix`, `gabbro`, `asterix`) - `-N`: list the tests that would run instead of running them - `--rerun-failed`: re-run only what failed last time - `-j N`: run N tests at once (the default is one per core) - `--stop-on-failure`: stop at the first failure instead of running on and `pbl test`’s own options: - `-C` / `--coverage`: collect coverage and generate an lcov HTML report at `build-test/lcov-html/index.html` - `--no-images`: skip the image fixtures, which only some tests need and which are the slow part of a clean build - `--build-only`: build the test binaries without running them Each test is a standalone binary under `build-test/`, so a failing one can be run directly or under a debugger: ``` gdb build-test/fw/ui/test_animation/runme ``` Results are written as JUnit XML to `build-test/junit.xml`, and a `compile_commands.json` for the tests lands in `build-test/`. The `Test` GitHub Actions workflow (`.github/workflows/test.yml`) runs the whole suite on pull requests that touch source, test or tooling paths. Test binaries link against the DUMA memory checker by default, so out-of-bounds heap accesses abort the test immediately. ## Writing a test A test is a C file named `test_.c` under a `tests/` subdirectory, plus a `pbl_clar_test()` entry in that directory’s `CMakeLists.txt`. The clar generator (`tools/clar/clar.py`) scans the file for functions named `test___` — no manual registration is needed: ``` #include \#include \void test\_crc32\_\_initialize(void) {// optional: runs before each test case}void test\_crc32\_\_cleanup(void) {// optional: runs after each test case}void test\_crc32\_\_empty(void) {cl\_assert\_equal\_i(pbl\_crc32(0, NULL, 0), 0);} ``` The available assertion macros (`cl_assert`, `cl_assert_equal_i`, `cl_assert_equal_s`, `cl_must_pass`, …) are defined in `tests/test_includes/clar_asserts.h`. The `CMakeLists.txt` entry declares which product sources, fakes and test files to compile: ``` pbl\_clar\_test(test\_crc32) ``` `pbl_crc32()` lives in `libcrc`, one of the static libraries `pbl_clar_test()` always links along with `libutil` and `libbtutil`. A test or stub defining one of their functions, weak or not, replaces the library copy, since the linker only pulls an archive member for undefined symbols. Code that is not part of the always-linked libraries — and any fakes from `tests/fakes/` — is listed explicitly under `SOURCES`, as paths relative to the repository root: ``` pbl\_clar\_test(test\_pfsSOURCESfw/services/filesystem/pfs.ctests/fakes/fake\_rtc.cOVERRIDES dummy\_board) ``` `pbl_clar_test()` (defined in `cmake/modules/pbl_test.cmake`) builds one binary per test file/platform combination and registers it with ctest. Its arguments: - `SOURCE`: the test file, when it is not `.c` next to the `CMakeLists.txt` - `SOURCES`: code under test plus any fakes from `tests/fakes/`; `@PLATFORM@` and `@BITDEPTH@` in a path expand per platform - `PLATFORMS`: build the test once per listed platform (e.g. `obelix gabbro`); the default is a single generic platform - `DEFINES`, `INCLUDES`, `LIBS`: extra compile defines, include paths and link libraries - `OVERRIDES`: opt-in header override directories (see below) - `DEPENDS`: targets to build first, for a test that needs a generated header - `TEST_IMAGES`: the test renders against the image fixtures A source needed by several tests is compiled once and shared between them whenever they agree on every compile flag, so adding a test that matches an existing one costs a single translation unit. ## Fakes, stubs and header overrides Three mechanisms replace dependencies of the code under test: - **Fakes** (`tests/fakes/`): functional host implementations, e.g. `fake_rtc.c`, `fake_spi_flash.c`. Compile them in via `sources_ant_glob`. - **Stubs** (`tests/stubs/`): headers with no-op implementations, for dependencies the test never exercises. - **Header overrides** (`tests/overrides/`): directories mirroring the source tree whose headers shadow the real ones on the include path. `tests/overrides/default/` is always active for every test; other directories are opted into per test via `override_includes`. Read `tests/overrides/README.md` and `tests/overrides/default/README.md` before adding one — in particular, avoid adding to `default/`. ## Broken tests `pbl_test_broken()` in `tests/CMakeLists.txt` lists tests that are not built. Their `pbl_clar_test()` declarations stay where they are, so what the test needs is still on record when someone picks it back up. ## JavaScript tests The JavaScript sources in `sdk/include/` have their own mocha-based tests in `sdk/tests/include/`; they are not run by `pbl test`. See `sdk/tests/include/README.md` for how to run them with `npm test`. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/testing.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/testing.md). --- # Software bill of materials Source: https://developer.repebble.com/pebbleos/docs/development/sbom/ The firmware build can describe the third-party components that go into a firmware image as a [CycloneDX](https://cyclonedx.org) 1.6 software bill of materials (SBOM). Releases ship one SBOM per firmware image, next to the `.pbz`, `.bin` and `.elf` files. ## Generating an SBOM The SBOM is not part of the default build. Build it on request, after configuring as usual: ``` pbl build sbom ``` This writes `pebbleos.cdx.json` to the build directory. The SBOM requires the Ninja generator, which `pbl configure` uses. ## What it contains The SBOM is derived from the build itself, so it lists exactly the components of the configured board and variant: 1. Ninja lists every input of `pebbleos.elf` (sources, objects, prebuilt libraries, resources) and, from its dependency logs, every header each object was compiled with. 2. Each file is matched against the component metadata (see below) by path. Files that belong to no component are PebbleOS’s own code. 3. Every matched component is listed with its version, supplier, license and, when available, its package URL (purl) and CPE. The top-level component is the firmware image itself, with its version (from `git describe`) and the SHA-256 and SHA-512 hashes of the firmware binary. Generation fails if a file from outside the tree, for example a toolchain header, belongs to no component, or if a file lies in a submodule’s own `nested` third-party directory without a component of its own. This keeps new dependencies from being left out silently. ## Component metadata Components are described in `sbom.yml` files: - `third_party/sbom.yml`: the submodules - `fw/sbom.yml`, `lib/sbom.yml`: third-party code kept in the tree - `cmake/toolchain/sbom.yml`: the toolchain runtime (C library, libgcc) Each entry has these fields: | Field | Description | | --- | --- | | `name` | Component name, unique across all files. | | `path` / `paths` | Files or directories, relative to the `sbom.yml`. | | `toolchain-path` | For toolchain components, a path relative to the toolchain root. | | `build-paths` | Paths relative to the build directory, for components installed there. | | `nested` | Subdirectories holding third-party code that needs its own component. | | `supplier` | Who supplies the component. | | `license` | SPDX license expression. Use `LicenseRef-` for licenses with no SPDX identifier. | | `version` | Upstream version. Defaults to `commit`. | | `version-from` | `compiler`, or a `file` and `define` to read the version from. | | `commit` | For components inside a submodule, the submodule commit. | | `type` | CycloneDX component type: `library` (default), `firmware` or `data`. | | `purl`, `cpe` | Identifiers for vulnerability databases. `{version}` is substituted. | | `url` | Upstream repository or website. | | `description` | Short description. | ### Updating a submodule The `commit` of every component inside a submodule must match the commit the submodule points to. When updating a submodule, update its `version`, `commit` and `purl` too. CI checks this on every pull request, as well as that every submodule is described. To run the same check locally: ``` python3 tools/cmake/sbom.py check ``` ### Adding third-party code New submodules and third-party code copied into the tree need an entry in the nearest `sbom.yml`. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/sbom.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/sbom.md). --- # The pbl CLI Source: https://developer.repebble.com/pebbleos/docs/development/pbl/ `pbl` is the developer command line for this repository: it configures and builds the firmware, puts the result on a device or an emulator, and hosts the operational commands around that. It is a Python package, installed along with the rest of the project’s dependencies: ``` pip install -r requirements.txt ``` After that `pbl` is on the `PATH` whenever the virtual environment is active. Run it from anywhere inside the checkout: ``` pbl configure --board asterix pbl build pbl flash ``` `pbl --help` lists every command, grouped; `pbl --help` shows one command’s own options. ## Shared options Two options are accepted by every command, before or after the command name: - `-b, --build-dir DIR` — use a different build directory. It defaults to the one `pbl.yml` names (`build/`, or `build-test/` for `pbl test`), and `PBL_BUILD_DIR` overrides that in turn. - `--dry-run` — print the commands that would run instead of running them. ## Where its knowledge comes from `pbl` holds no facts about the checkout of its own: - The **workspace** is wherever `pbl.yml` is, found by walking up from the current directory. That file also names the build and test directories. - The **build** is read back from its own byproducts: `.config` for every `CONFIG_` symbol, `CMakeCache.txt` for the board, the variant and the project name the artifacts are named after. Configuring is therefore the only way to tell `pbl` about a build, and reconfiguring is enough to change its mind. - The **board** contributes the rest through `boards//.yml`: its revisions, the binary runners it supports, and the SDL decorations the emulator can draw around its screen. ## Structure The package lives in `tools/libs/pbl-cli/`, and is a small core plus one module per command, in the shape of Zephyr’s `west`: ``` pbl/app/main.pyfindstheworkspace,assemblesthecommands,dispatchespbl/command.pyPblCommand,thebaseclass everycommandderivesfrompbl/workspace.pythecheckout:pbl.yml,thelayout,theextensionspbl/build.pyabuilddirectoryandwhatcanbereadbackfrom itpbl/runners.pythebinaryrunners(openocd,sftool,nrfutil)pbl/commands/thecommandsthemselves ``` ### Adding a command Drop a module in `pbl/commands/` with a `PblCommand` subclass. Commands are discovered by scanning the package, so there is no registry to update; classes whose name starts with an underscore are treated as shared bases rather than commands. ``` from pbl.command importPblCommandclass Size(PblCommand):group="build"def \_\_init\_\_(self):super().\_\_init\_\_("size","Report the firmware's size")def do\_add\_parser(self,parser\_adder):parser=self.add\_subparser(parser\_adder)parser.add\_argument("--sections",action="store\_true")returnparserdef do\_run(self,args,unknown):build=self.build\_dir()returnself.run\_cmd(["arm-none-eabi-size",build.elf]) ``` `self.build_dir()` returns the configured build, failing with a useful message when there is none; `self.run_cmd()` and `self.check_cmd()` run things from the workspace root and honor `--dry-run`; raising `CommandError` is how a command fails. `group` picks the section of the top-level help the command is listed under. ### Feeds `pbl feed ` writes simulated phone data into the emulator. Each feed is a module in `pbl/feeds/` with a `Feed` subclass, discovered the same way commands are: it names its subcommand, adds its own options in `add_arguments()` and writes its data in `run()` through the `Watch` it is given, which wraps the blob DB client, sends and receives raw protocol packets, and turns into a printer under `--dry-run`. A feed that has to answer the watch registers handlers with `watch.on()` and keeps `run()` going until interrupted. ``` from pbl.feeds importBlobDb,Feedclass Steps(Feed):name="steps"help="A day of step counts"def add\_arguments(self,parser):parser.add\_argument("--steps",type=int,default=8000)def run(self,args,watch,inf):watch.blobdb\_insert(BlobDb.HEALTH,key,value)inf(f"pushed {args.steps} steps") ``` ### Extension commands A command that should not live in the package is declared in `pbl.yml` instead. It is only imported when it is the command being run, and is listed under “extension commands” rather than in one of the groups: ``` commands:- file: tools/pbl\_commands/size.pycommands:- name: sizeclass: Sizehelp: Report the firmware's size ``` ## Binary runners Flashing and debugging a real device go through a runner, the same abstraction Zephyr uses. A board lists the ones it supports in its manifest, first one first: ``` runners:- openocd- nrfutil ``` `pbl flash`, `reset`, `run`, `bork`, `openocd` and `debug` all dispatch to it, and `-r/--runner` picks a different one. Each runner contributes its own options (e.g. `--tty` for sftool), so they show up on all of those commands. New runners are modules in `pbl/runners/`, registered in that package’s `RUNNERS` table. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/development/pbl.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/development/pbl.md). --- # Architecture overview Source: https://developer.repebble.com/pebbleos/docs/architecture/ A guided tour of how PebbleOS is put together, deep-linking to the design prose that lives in the source tree. The in-source comments are canonical — these pages summarize and point, they do not duplicate. ## Layering PebbleOS runs on its own [kernel API](https://developer.repebble.com/pebbleos/docs/architecture/kernel.md) (`include/pbl/kernel`, implemented under `kernel/`). The main source layers, as described on the [API reference](https://developer.repebble.com/pebbleos/apidoc/index.html) main page: - `fw/applib` — application framework and UI, the API surface exposed to watchapps ([SDK export](https://developer.repebble.com/pebbleos/docs/development/sdk_export.md) describes how functions get there). - `fw/services` — system services (Bluetooth, filesystem, activity, …). - `fw/kernel` — task management, events, memory. - `drivers/` — hardware drivers (public interfaces under `include/pbl/drivers`). - `subsys/` — OS subsystems shared beyond the firmware tree; currently logging, cron, the Bluetooth backends, [CRC](https://developer.repebble.com/pebbleos/docs/architecture/crc.md), the [debug shell](https://developer.repebble.com/pebbleos/docs/architecture/shell.md) and the [task watchdog](https://developer.repebble.com/pebbleos/docs/architecture/task_watchdog.md), included via the `pbl/logging/`, `pbl/cron/`, `pbl/bluetooth/`, `pbl/crc/`, `pbl/shell/` and `pbl/task_wdt/` header paths. Alongside these sit `fw/shell` (launcher/watchface UX flow), `fw/process_management` (app lifecycle) and `fw/comm` (phone communication). ## Task model The firmware runs a fixed set of kernel threads, enumerated in `fw/kernel/pebble_tasks.h`: KernelMain, KernelBackground, Worker, App, the Bluetooth tasks (host, controller, HCI), NewTimers and [PULSE](https://developer.repebble.com/pebbleos/docs/reference/pulse2/pulse2.md). `main()` (`fw/main.c`) performs SoC init and spawns KernelMain, which brings up the rest of the system. ## Boot and firmware variants The bootloader is not part of this tree; it selects which firmware image to launch, coordinated through boot bits (see `BOOT_BIT_*` usage in `fw/main.c`) and the slot metadata in `fw/system/firmware_storage.h`. Normal firmware and PRF (Pebble Recovery Firmware — the minimal fallback image used to reinstall the main firmware) are separate compile-time variants: `pbl configure --variant=prf` (see [build options](https://developer.repebble.com/pebbleos/docs/development/options.md)) applies `fw/prj_prf.conf` on top of the base config, disabling the JS engine and marking the image as recovery firmware. ## Processes and apps - **App identity** — an installed app is referred to by several identifiers (`AppInstallId`, `AppInstallEntry`, UUID, `PebbleProcessMd`); the comment at the top of `fw/process_management/app_install_manager.h` explains which to use where and which are deprecated. - **Shell flow** — which app launches at startup and what happens when an app exits is a deliberately flat state machine rooted in the launcher and the watchface; see the diagrammed comment at the top of `fw/shell/normal/system_app_state_machine.c`. - **Privilege boundary** — third-party app and worker code runs unprivileged (processes built into the firmware stay privileged); anything touching OS state crosses into the kernel through a `sys_*` syscall (declared in `fw/syscall/syscall.h`) defined with `DEFINE_SYSCALL` from `fw/syscall/syscall_internal.h`, which raises privileges on entry and drops them on return unless the caller was already privileged. ## Memory layout `fw/linker/pebbleos.ld` (“Section concepts!” comment) documents the flash/RAM picture: VMA vs LMA, the kernel data/bss/stack/heap region, and the fixed app region where third-party app code, data and heap live. `fw/linker/memory.ld` explains how SRAM is carved between kernel, app and worker regions, and `fw/linker/regions.ld` notes the MPU power-of-two constraints. ## Bluetooth `fw/comm/ble/` is the host-side BLE layer and carries substantial design prose: - `gap_le_connect.c` — connection management and the “connection intent” abstraction that virtualizes the link across multiple clients. - `gap_le_advert.c` — round-robin scheduling of multiple advertising jobs onto a controller that only holds one payload at a time (the comment predates the current controllers, but the scheduler it describes is still in use). - `gap_le_connect_params.c` — a primer on connection intervals, slave latency and supervision timeouts, and why the firmware deviates from the spec-recommended parameter-update pause for iOS. Beneath it sits NimBLE (`third_party/nimble`), glued in by `subsys/bluetooth/`. The HCI transport is chosen per SoC (`BT_HCI`): the NimBLE controller on the nRF52 radio, the SiFli LCPU over IPC on SF32LB52, and a fake controller on QEMU that acknowledges every command so the host runs without a radio. On QEMU the phone link is the emulator’s serial channel, `fw/comm/qemu/`. ## Storage PFS, the Pebble File System, lives in `fw/services/filesystem/`. The API and on-flash layout are documented in `pfs.h`; the wear-leveling strategy (round-robin page allocation tracking the last written page) is described alongside the allocator in `pfs.c`. ## Drivers - **Accelerometer** — `include/pbl/drivers/accel.h` explains the split between the dumb low-level driver and the accel service that owns buffering, clients and subsampling, so the same service code runs on any accel part. - **Flash** — `drivers/flash/README.md` documents the two flash APIs: the main one, and a coredump-only path that must work without OS services. ## Coredumps The on-flash coredump image format (header plus chunked records, including per-thread register sets) is documented in `fw/kernel/core_dump.c`. ## Design documents Longer design documents live as their own pages: - [Health Algorithms](https://developer.repebble.com/pebbleos/docs/architecture/activity.md) - [CRC](https://developer.repebble.com/pebbleos/docs/architecture/crc.md) - [Kernel](https://developer.repebble.com/pebbleos/docs/architecture/kernel.md) - [Kernel internals](https://developer.repebble.com/pebbleos/docs/architecture/kernel_internals.md) - [Shell](https://developer.repebble.com/pebbleos/docs/architecture/shell.md) - [Task watchdog](https://developer.repebble.com/pebbleos/docs/architecture/task_watchdog.md) Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/architecture/index.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/architecture/index.md). --- # CRC Source: https://developer.repebble.com/pebbleos/docs/architecture/crc/ `subsys/crc/` computes every checksum the firmware stores or exchanges. Its public API is `include/pbl/crc/crc.h`: | Function | Algorithm | Used by | | --- | --- | --- | | `pbl_crc8()`, `pbl_crc8_reversed()` | CRC-8, polynomial 0x2F (CRC-8/OPENSAFETY) | PFS page chaining, settings file key hashes | | `pbl_crc32()` | CRC-32/ISO-HDLC, zlib-compatible | PULSE2, flash CRC, shared PRF storage, boot bits | | `pbl_crc32_legacy_*()` | CRC-32/MPEG-2 over little-endian words | PFS, app images, resources, data logging, PULSE, put bytes | The legacy checksum reproduces what the STM32F2/F4 CRC unit computed when the firmware fed it 32-bit words, including the zero padding of a trailing partial word. It lives on in on-flash and on-wire formats, so its definition cannot change; `crc.h` spells it out. ## Layering The layering follows Linux’s `lib/crc`. The public functions own the software implementations, which are always built, and decide when to hand work to hardware: ``` pbl_crc32() / pbl_crc32_legacy_update() subsys/crc ├── len >= CONFIG_CRC_HW_MIN_LEN ──► pbl_crc_hw_*() driver, optional └── remainder ──────────────────────► software, nybble-wide tables ``` A driver offering acceleration selects the hidden `CONFIG_CRC_HW_ACCEL` symbol and implements the two functions in `include/pbl/drivers/crc.h`. Each takes a running value and a buffer, processes a prefix of it that is a whole number of 32-bit words, and returns its length in bytes. Returning less than the full length is always allowed: the subsystem finishes in software. A driver returns 0 whenever the hardware cannot be used — before it is ready, when its self-test failed, or for any other reason — so callers never see a failure. `CONFIG_CRC_HW_MIN_LEN` keeps short buffers in software, where programming the peripheral costs more than it saves. CRC-8 inputs are a few bytes long and are never offloaded. ## SF32LB `drivers/crc/sf32lb.c` (`CONFIG_CRC_SF32LB`, default on SF32LB52) drives the CRC1 unit. It programs the polynomial, initial value and reflection for every burst of up to 256 bytes under `pbl_irq_lock()`, carrying the running value between bursts, so the unit needs no owner. That works before the scheduler starts and from interrupt context, and bounds the interrupt latency it adds to a few microseconds. At 240 MHz a call costs about 330 cycles of setup, then about 5.4 cycles per byte against about 20 for the software tables. The unit breaks even around 24 bytes and is about 3.6 times faster on large buffers, which sets the `CONFIG_CRC_HW_MIN_LEN` default of 32. On first use the driver checks both algorithms against fixed test vectors, in one burst and resumed across two, and disables itself on any mismatch, logging an error. The software path then serves every request. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/architecture/crc.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/architecture/crc.md). --- # Health Algorithms Source: https://developer.repebble.com/pebbleos/docs/architecture/activity/ ## Step Counting The step counting algorithm uses input from the accelerometer sensor to detect when the user is walking and how many steps have been taken. The accelerometer measures acceleration in each of 3 axes: x, y, and z. A perfectly still watch resting flat on a table will have 1G (1 “Gravity”) of acceleration in the z direction (due to gravity) and 0G’s in both the x and y axes. If you tilt the watch on its side for example, the z reading will go to 0 and then either x or y will show +/-1G (depending on which of the 4 sides you tilt it to). During watch movement, the x, y, and z readings will vary over time due to the watch’s changing orientation to gravity as well as the acceleration of the watch when it changes direction or speed. The pattern of these variations in the accelerometer readings over time can be used to detect if, and how fast, the user is stepping. There are generally two dominant signals that show up in the accelerometer readings when a person is walking or running. The first is the signal due to your feet hitting the ground. This signal shows up as a spike in the accelerometer readings each time a foot hits the ground and will be more or less pronounced depending on the cushioning of your shoes, the type of flooring, etc. Another signal that can show up is from the arm swinging motion, and the strength of this will vary depending on the user’s walking style, whether their hand is in their pocket or not, whether they are carrying something, etc. Of these two signals, the foot fall one is the most reliable since a user will not always be swinging their arms when walking. The goal of the step tracking algorithm is to isolate and detect this foot fall signal, while not getting confused by other signals (arm swings, random arm movements, etc.). An overall outline of the approach taken by the stepping algorithm (glossing over the details for now) is as follows: 1. Separate the accelerometer sensor readings into 5 second epochs. 2. For each 5 second epoch, compute an FFT (Fast Fourier Transform) to get the energy of the signal at different frequencies (called the _spectral density_) 3. Examine the FFT output using a set of heuristics to identify the foot fall signal (if present) and its frequency. 4. The frequency of the foot fall signal (if present) is outputted as the number of steps taken in that epoch. As an example, if the FFT of a 5 second epoch shows a significant amount of foot fall signal at a frequency of 2Hz, we can assume the person has walked 10 steps (2Hz x 5 seconds) in that epoch. ### Example Data The following figure shows an example of the raw accelerometer data of a five-second epoch when a user is walking 10 steps. The x, y, and z axis signals are each shown in a different color. In this plot, there is a fairly evident five-cycle rhythm in the red and green axes, which happens to be the arm swing signal (for every 2 steps taken, only 1 full arm swing cycle occurs). The ten-cycle foot fall signal however is difficult to see in this particular sample because the arm swing is so strong. ![Raw accelerometer data](https://developer.repebble.com/pebbleos/docs/images/raw_accel_5s.png) The spectral density of that same walk sample, showing the amount of energy present at different frequencies, is shown in the following figure (this particular plot was generated from a sample longer than 5 seconds, so will be less noisy than any individual 5 second epoch). Here, the spectral density of the x, y, and z axes as well as the combined signal are each plotted in a different color. The _combined_ signal is computed as the magnitude of the x, y, and z spectral density at each frequency: combined[f] = sqrt(x[f]^2 + y[f]^2 + z[f]^2) _Note that the y axis on this plot is not simply Power, but rather “Power / Average Power”, where “Average Power” is the average power of that particular signal._ ![Spectral Density](https://developer.repebble.com/pebbleos/docs/images/spectial_density.png) You can see in the above spectral density plot that the dominant frequency in this example is 1Hz, corresponding to the 5 arm swings that occurred in these 5 seconds. There are also several smaller peaks at the following frequencies: - ~.25Hz: non-stepping signal, most likely random arm movements - 2Hz: stepping frequency + 2nd harmonic of arm swing - 3Hz: 3rd harmonic of arm swing - 4Hz: 2nd harmonic of steps + 4th harmonic of arm swing - 5Hz: 5th harmonic of arm swing - 8Hz: 4th harmonic of steps The logic used to pull out and identify the stepping signal from the spectral density output will be described later, but first we have to introduce the concept of VMC, or Vector Magnitude Counts. ### VMC VMC, or Vector Magnitude Counts, is a measure of the overall amount of movement in the watch over time. When the watch is perfectly still, the VMC will be 0 and greater amounts of movement result in higher VMC numbers. Running, for example results in a higher VMC than walking. The VMC computation in Pebble Health was developed in conjunction with the Stanford Wearables lab and has been calibrated to match the VMC numbers produced by the [Actigraph](https://ametris.com/actigraph-leap) wrist-worn device. The Actigraph is commonly used today for medical research studies. The Stanford Wearables lab will be publishing the VMC computation used in the Pebble Health algorithm and this transparency of the algorithm will enable the Pebble to be used for medical research studies as well. VMC is computed using the formula below. Before the accelerometer readings are incorporated into this computation however, each axis’ signal is run through a bandpass filter with a design of 0.25Hz to 1.75Hz. ![](https://developer.repebble.com/pebbleos/docs/images/vmc_formula.png) The following pseudo code summarizes the VMC calculation for N samples worth of data in each axis. The accelerometer is sampled at 25 samples per second in the Health algorithm, so the VMC calculation for 1 second’s worth of data would process 25 samples from each axis. The _bandpass\_filter_ method referenced in this pseudo code is a convolution filter with a frequency response of 0.25 to 1.75Hz: ``` for each axis in x, y, z: axis_sum[axis] = 0 for each sample in axis from 0 to N: filtered_sample = bandpass_filter(sample, filter_state) axis_sum[axis] += abs(filtered_sample) VMC = scaling_factor * sqrt(axis_sum[x]^2 + axis_sum[y]^2 + axis_sum[z]^2) ``` The step algorithm makes use of a VMC that is computed over each 5-second epoch. In addition to this 5-second VMC, the algorithm also computes a VMC over each minute of data. It saves this 1-minute VMC to persistent storage and sends it to data logging as well so that it will eventually get pushed to the phone and saved to a server. The 1-minute VMC values stored in persistent storage can be accessed by 3rd party apps through the Health API. It is these 1-minute VMC values that are designed to match the Actigraph computations and are most useful to medical researcher studies. ### Step Identification As mentioned above, accelerometer data is processed in chunks of 5 seconds (one epoch) at a time. For each epoch, we use the combined spectral density (FFT output) and the 5-second VMC as inputs to the step identification logic. #### Generating the FFT output The accelerometer is sampled at 25Hz, so each 5 second epoch comprises 125 samples in each axis. An FFT must have an input width which is a power of 2, so for each axis, we subtract the mean and then 0-extend to get 128 samples before computing the FFT for that axis. An FFT of a real signal with 128 samples produces 128 outputs that represent 64 different frequencies. For each frequency, the FFT produces a real and an imaginary component (thus the 128 outputs for 64 different frequencies). The absolute value of the real and imaginary part denote the amplitude at a particular frequency, while the angle represents the phase of that frequency. It is the amplitude of each frequency that we are interested in, so we compute sqrt(real^2 + imag^2) of each frequency to end up with just 64 outputs. Once the 64 values for each of the 3 axes have been computed, we combine them to get the overall energy at each frequency as follows: ``` for i = 0 to 63: energy[i] = sqrt(amp_x[i]^2 + amp_y[i]^2 + amp_z[i]^2) ``` In this final array of 64 elements, element 0 represents the DC component (0 Hz), element 1 represents a frequency of 1 cycle per epoch (1 / 5s = 0.2Hz), element 2 represents a frequency of 2 cycles per epoch (2 / 5s = 0.4Hz), etc. If the user is walking at a rate of 9 steps every 5 seconds, then a spike will appear at index 9 in this array (9 / 5s = 1.8Hz). As an example, the following shows the FFT output of a user walking approximately 9 steps in an epoch (with very little arm swing): ![FFT of stepping epoch, 9 steps](https://developer.repebble.com/pebbleos/docs/images/fft_walking.png) #### Determining the stepping frequency Once the FFT output and VMC have been obtained, we search for the most likely stepping frequency. The naive approach is to simply locate the frequency with the highest amplitude among all possible stepping frequencies. That would work fine for the example just shown above where there is a clear peak at index 9 of the FFT, which happens to be the stepping frequency. However, for some users the arm swinging signal can be as large or larger than the stepping signal, and happens to be at half the stepping frequency. If a user is walking at a quick pace, the arm swinging signal could easily be misinterpreted as the stepping signal of a slow walk. The following is the FFT of such an example. The stepping signal shows up at indices 9 and 10, but there is a larger peak at the arm-swing frequency at index 5. ![Stepping epoch with large arm-swing component](https://developer.repebble.com/pebbleos/docs/images/fft_arm_swing.png) To deal with these possible confusions between arm-swing and stepping signals, the VMC is used to narrow down which range of frequencies the stepping is likely to fall in. Based on the VMC level, we search one of three different ranges of frequencies to find which frequency has the most energy and is the likely stepping frequency. When the VMC is very low, we search through a range of frequencies that represent a slow walk, and for higher VMCs we search through ranges of frequencies that represent faster walks or runs. Once we find a stepping frequency candidate within the expected range, we further refine the choice by factoring in the harmonics of the stepping/arm swinging. Occasionally, a max signal in the stepping range does not represent the actual stepping rate - it might be off by one or two indices due to noise in the signal, or it might be very close in value to the neighboring frequency, making it hard to determine which is the optimal one to use. This is evident in the arm-swinging output shown above where the energy at index 9 is very close to the energy at index 10. As mentioned earlier, we often see significant energy at the harmonics of both the arm-swinging and the stepping frequency. A harmonic is an integer multiple of the fundamental frequency (i.e. a stepping frequency of 2 Hz will result in harmonics at 4Hz, 6Hz, 8Hz, etc.). To further refine the stepping frequency choice, we evaluate all possible stepping frequencies near the first candidate (+/- 2 indices on each side) and add in the energy of the harmonics for each. For each evaluation, we add up the energy of that stepping frequency, the arm energy that would correspond to that stepping frequency (the energy at half the stepping frequency), and the 2nd thru 5th harmonics of both the stepping and arm-swinging frequencies. Among these 5 different candidate stepping frequencies, we then choose the one that ended up with the most energy overall. At the end of this process, we have the most likely stepping frequency, **if** the user is indeed walking. The next step is to determine whether or not the user is in fact walking or not. #### Classifying step vs non-step epochs In order to classify an epoch as walking or non-walking, we compute and check a number of metrics from the FFT output. The first such metric is the _walking score_ which is the sum of the energy in the stepping related frequencies (signal energy) divided by the sum of energy of all frequencies (total energy). The signal energy includes the stepping frequency, arm-swing frequency, and each of their harmonics. If a person is indeed walking, the majority of the signal will appear at these signal frequencies, yielding a high walking score. The second constraint that the epoch must pass is that the VMC must be above a _minimum stepping VMC_ threshold. A higher threshold is used if the detected stepping rate is higher. The third constraint that the epoch must pass is that the amount of energy in the very low frequency components must be relatively low. To evaluate this constraint, the amount of energy in the low frequency components (indices 0 through 4) is summed and then divided by the signal energy (computed above). If this ratio is below a set _low frequency ratio_ threshold, the constraint is satisfied. The example below is typical of many epochs that are non-stepping epochs - a large amount of the energy appears in the very low frequency area. ![Non-stepping epoch](https://developer.repebble.com/pebbleos/docs/images/fft_non_walk.png) The fourth and final constraint that the epoch must pass is that the energy in the high frequencies must be relatively low. To evaluate this constraint, the amount of energy in the high frequency components (index 50 and above) is summed and then divided by the signal energy. If this ratio is below a set _high frequency ratio_ threshold, the constraint is satisfied. This helps to avoid counting driving epochs as stepping epochs. In many instances, the vibration of the engine in a car will show up as energy at these high frequencies as shown in the following diagram. ![Driving epoch](https://developer.repebble.com/pebbleos/docs/images/fft_driving.png) #### Partial Epochs If the user starts or ends a walk in the middle of an epoch, the epoch will likely not pass the checks for a full fledged stepping epoch and these steps will therefore not get counted. To adjust for this undercounting, the algorithm introduces the concept of _partial epochs_. The required _walking score_ and _minimum VMC_ are lower for a partial epoch vs. a normal epoch and there are no constraints on the low or high frequency signal ratios. To detect if an epoch is a _partial epoch_ we only check that the _walking score_ is above the _partial epoch walking score_ threshold and that the VMC is above the _partial epoch minimum VMC_ threshold. If we detect a partial epoch, and either the prior or next epoch were classified as a stepping epoch, we add in half the number of steps that were detected in the adjacent stepping epoch. This helps to average out the undercounting that would normally occur at the start and end of a walk. For a very short walk that is less than 2 epochs long though, there is still a chance that no steps at all would be counted. * * * ## Sleep Tracking The sleep tracking algorithm uses the minute-level VMC values and minute-level average orientation of the watch to determine if/when the user is sleeping and whether or not the user is in “restful” sleep. The minute-level VMC was described above. It gives a measure of the overall amount of movement seen by the watch in each minute. The average orientation is a quantized (currently 8 bits) indication of the 3-D angle of the watch. It is computed once per minute based on the average accelerometer reading seen in each of the 3 axes. The angle of the watch in the X-Y plane is computed and quantized into the lower 4 bits and the angle of that vector with the Z-axis is then quantized and stored in the upper 4 bits. ### Sleep detection The following discussion uses the term _sleep minute_. To determine if a minute is a _sleep minute_, we perform a convolution of the VMC values around that minute (using the 4 minutes immediately before and after the given minute) to generate a _filtered VMC_ and compare the _filtered VMC_ value to a threshold. If the result is below a determined sleep threshold, we count it as a _sleep minute_. A rough outline of the sleep algorithm is as follows. 1. Sleep is entered if there are at least 5 _sleep minutes_ in a row. 2. Sleep continues until there are at least 11 non-_sleep minutes_ in a row. 3. If there were at least 60 minutes between the above sleep enter and sleep exit times, it is counted as a valid sleep session. There are some exceptions to the above rules however: - After sleep has been entered, if we see any minute with an exceptionally high _filtered VMC_, we end the sleep session immediately. - If it is early in the sleep session (the first 60 minutes), we require 14 non-_sleep minutes_ in a row to consider the user as awake instead of 11. - If at least 80% of the minutes have slight movement in them (even if each one is not high enough to make it a non-_sleep minute_), we consider the user awake. - If we detect that the watch was not being worn during the above time (see below), we invalidate the sleep session. #### Restful sleep Once we detect a sleep session using the above logic, we make another pass through that same data to see if there are any periods within that session that might be considered as _restful sleep_. A _restful sleep minute_ is a minute where the _filtered VMC_ is below the _restful sleep minute_ threshold (this is lower than the normal _sleep minute_ threshold). 1. Restful sleep is entered if there are at least 20 _restful sleep minutes_ in a row. 2. Restful sleep continues until there is at least 1 minute that is not a _restful sleep minute_. ### Detecting not-worn Without some additional logic in place, the above rules would think a user is in a sleep session if the watch is not being worn. This is because there would be no movement and the VMC values would all be 0, or at least very low. Once we detect a possible sleep session, we run that same data through the “not-worn” detection logic to determine if the watch was not being worn during that time. This is a set of heuristics that are designed to distinguish not-worn from sleep. The following description uses the term _not worn minute_. A _not worn minute_ is a minute where **either** of the following is true: - The VMC (the raw VMC, not _filtered VMC_) is below the _not worn_ threshold and the average orientation is same as it was the prior minute - The watch is charging If we see **both** of the following, we assume the watch is not being worn: 1. There are at least 100 _not worn_ minutes in a row in the sleep session 2. The _not worn_ section from #1 starts within 20 minutes of the start of the candidate sleep session and ends within 10 minutes of the end of the candidate sleep session. The 100 minute required run length for _not worn_ might seem long, but it is not uncommon to see valid restful sleep sessions for a user that approach 100 minutes in length. The orientation check is useful for situations where a watch is resting on a table, but encounters an occasional vibration due to floor or table shaking. This vibration shows up as a non-zero VMC and can look like the occasional movements that are normal during sleep. During actual sleep however, it is more likely that the user will change positions and end up at a different orientation on the next minute. * * * ## System Integration The following sections discuss how the step and sleep tracking algorithms are integrated into the firmware. ### Code organization The core of the Health support logic is implemented in the activity service, which is in the `fw/services/activity` directory. The 3rd party API, which calls into the activity service, is implemented in `fw/applib/health_service.c.` The activity service implements the step and sleep algorithms and all of the supporting logic required to integrate the algorithms into the system. It has the following directory structure: ``` fw/services/activity activity.c activity_insights.c kraepelin/ kraepelin_algorithm.c activity_algorithm_kraepelin.c ``` - **activity.c** This is the main module for the activity service. It implements the API for the activity service and the high level glue layer around the underlying step and sleep algorithms. This module contains only algorithm agnostic code and should require minimal changes if an alternative implementation for step or sleep tracking is incorporated in the future. - **activity\_insights.c** This module implements the logic for generating Health timeline pins and notifications. - **kraepelin** This subdirectory contains the code for the Kraepelin step and sleep algorithm, which is the name given to the current set of algorithms described in this document. This logic is broken out from the generic interface code in activity.c to make it easier to substitute in alternative algorithm implementations in the future if need be. - **kraepelin\_algorithm.c** The core step and sleep algorithm code. This module is intended to be operating system agnostic and contains minimal calls to external functions. This module originated from open source code provided by the Stanford Wearables Lab. - **kraepelin/activity\_algorithm\_kraepelin.c** This module wraps the core algorithm code found in `kraepelin_algorithm.c` to make it conform to the internal activity service algorithm API expected by activity.c. An alternative algorithm implementation would just need to implement this same API in order for it to be accessible from `activity.c`. This modules handles all memory allocations, persistent storage management, and other system integration functions for the raw algorithm code found in kraepelin\_algorithm.c. The 3rd party Health API is implemented in `fw/applib/health_service.c`. The `health_service.c` module implements the “user land” logic for the Health API and makes calls into the activity service (which runs in privileged mode) to access the raw step and sleep data. ### Step Counting The `activity.c` module asks the algorithm implementation `activity_algorithm_kraepelin.c` what accel sampling rate it requires and handles all of the logic required to subscribe to the accel service with that sampling rate. All algorithmic processing (both step and sleep) in the activity service is always done from the KernelBG task, so `activity.c` subscribes to the accel service from a KernelBG callback and provides the accel service a callback method which is implemented in `activity.c`. When `activity.c’s` accel service callback is called, it simply passes the raw accel data onto the underlying algorithm’s accel data handler implemented `activity_algorithm_kraepelin.c`. This handler in turn calls into the core algorithm code in `kraepelin_algorithm.c` to execute the raw step algorithm code and increments total steps by the number of steps returned by that method. Since the step algorithm in `kraepelin_algorithm.c` is based on five-second epochs and the accel service callback gets called once a second (25 samples per second), the call into `kraepelin_algorithm.c` will only return a non-zero step count value once every 5 times it is called. Whenever a call is made to `activity.c` to get the total number of steps accumulated so far, `activity.c` will ask the `activity_algorithm_kraepelin.c` module for that count. The `activity_algorithm_kraepelin.c` module maintains that running count directly and returns it without needing to call into the raw algorithm code. At midnight of each day, `activity.c` will make a call into `activity_algorithm_kraeplin.c` to reset the running count of steps back to 0. ### Sleep processing For sleep processing, the `activity_algorithm_kraepelin.c` module has a much bigger role than it does for step processing. The core sleep algorithm in `kraepelin_algorithm.c` simply expects an array of VMC and average orientation values (one each per minute) and from that it identifies where the sleep sessions are. It is the role of `activity_algorithm_kraepelin.c` to build up this array of VMC values for the core algorithm and it does this by fetching the stored VMC and orientation values from persistent storage. The `activity_algorithm_kraepelin.c` module includes logic that periodically captures the VMC and orientation for each minute from the core algorithm module and saves those values to persistent storage for this purpose as well as for retrieval by the 3rd party API call that can be used by an app or worker to fetch historical minute-level values. Currently, `activity.c` asks `activity_algorithm_kraepelin.c` to recompute sleep every 15 minutes. When asked to recompute sleep, `activity_algorithm_kraepelin.c` fetches the last 36 hours of VMC and orientation data from persistent storage and passes that array of values to the core sleep algorithm. When we compute sleep for the current day, we include all sleep sessions that _end_ after midnight of the current day, so they may have started sometime before midnight. Including 36 hours of minute data means that, if asked to compute sleep at 11:59pm for example, we can go as far back as a sleep session that started at 6pm the prior day. To keep memory requirements to a minimum, we encode each minute VMC value into a single byte for purposes of recomputing sleep. The raw VMC values that we store in persistent storage are 16-bit values, so we take the square root of each 16-bit value to compress it into a single byte. The average orientation is also encoded as a single byte. The 36 hours of minute data therefore requires that an array of 36 \* 60 \* 2 (4320) bytes be temporarily allocated and passed to the core sleep algorithm logic. The core sleep logic in `kraepelin_algorithm.c` does not have any concept of what timestamp corresponds to each VMC value in the array, it only needs to describe the sleep sessions in terms of indices into the array. It is the role of `activity_algorithm_kraepelin.c` to translate these indices into actual UTC time stamps for use by the activity service. ## Algorithm Development and Testing There is a full set of unit tests in `tests/fw/services/activity` for testing the step and sleep algorithms. These tests run captured sample data through the `kraepelin_algorithm.c` algorithm code to verify the expected number of steps or sleep sessions. ### Step Algorithm Testing For testing the step algorithm, raw accel data is fed into the step algorithm. This raw accel data is stored in files as raw tuples of x, y z, accelerometer readings and can be found in the `tests/fixtures/activity/step_samples` directory. Although these files have the C syntax, they are not compiled but are read in and parsed by the unit tests at run-time. Each sample in each file contains meta-data that tells the unit test the expected number of steps for that sample, which is used to determine if the test passes or not. To capture these samples, the activity service has a special mode that can be turned on for raw sample capture and the `Activity Demo` app has an item in its debug menu for turning on this mode. When this mode is turned on, the activity service saves raw samples to data logging, and at the same time, also captures the raw sample data to the Pebble logs as base64 encoded binary data. Capturing the accel data to the logs makes it super convenient to pull that data out of the watch simply by issuing a support request from the mobile app. The `tools/activity/parse_activity_data_logging_records.py` script can be used to parse the raw accel samples out of a log file that was captured as part of a support request or from a binary file containing the data logging records captured via data logging. This tool outputs a text file, in C syntax, that can be used directly by the step tracking unit tests. The unit test that processes all of the step samples in `tests/fixtures/activity/step_samples` insures that the number of steps computed by the algorithm for each sample is within the allowed minimum and maximum for that sample (as defined by the meta data included in each sample file). It also computes an overall error amount across all sample files and generates a nice summary report for reference purposes. When tuning the algorithm, these summary reports can be used to easily compare results for various potential changes. ### Sleep Algorithm Testing For testing the sleep algorithm, minute-by-minute VMC values are fed into the algorithm code. The set of sample sleep files used by the unit tests are found in the `tests/fixtures/activity/sleep_samples` directory. As is the case for the step samples, these files are parsed by the unit tests at run-time even though they are in C syntax. To capture these samples, the activity service has a special call that will result in a dump of the contents of the last 36 hours of minute data to the Pebble logs. The `Activity Demo` app has an item in its debug menu for triggering this call. When this call is made, the activity service will fetch the last 36 hours of minute data from persistent storage, base64 encode it, and put it into the Pebble logs so that it can be easily retrieved using a support request from the mobile app. As is the case for step data, the `tools/activity/parse_activity_data_logging_records.py` script can also be used to extract the minute data out of a support request log file and will in turn generate a text file that can be directly parsed by the sleep algorithm unit tests. Each sleep sample file contains meta data in it that provides upper and lower bounds for each of the sleep metrics that can be computed by the algorithm (total amount of sleep, total amount of restful sleep, sleep start time, sleep end time, etc.). These metrics are checked by the unit tests to determine if each sample passes. Note that the minute-by-minute VMC values can always be captured up to 36 hours **after** a sleep issue has been discovered on the watch since the watch is always storing these minute statistics in persistent storage. In contrast, turning on capture of raw accel data for a step algorithm issue must be done before the user starts the activity since capturing raw accel data is too expensive (memory and power-wise) to leave on all the time. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/architecture/activity/index.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/architecture/activity/index.md). --- # Kernel Source: https://developer.repebble.com/pebbleos/docs/architecture/kernel/ `kernel/` owns every RTOS primitive the firmware uses. The public API lives in `include/pbl/kernel/` and is the only threading interface the rest of the tree may use. The implementation is PebbleOS’s own; its internals are described in [kernel internals](https://developer.repebble.com/pebbleos/docs/architecture/kernel_internals.md). ## Layout ``` include/pbl/kernel/publicAPI:types,irq,thread,mutex,sem,msgq,poll,sched,idle,debuginclude/pbl/kernel/backend.hper-objectprivatestatethepublicstructsembedinclude/pbl/kernel/compiler.hcompilerabstraction,backedbycompiler/gcc.handcompiler/clang.hinclude/pbl/kernel/section.hplacementofcodeanddatainspecialsectionskernel/scheduler,objects,tickconversionkernel/arch/arm/Cortex-Mport:contextswitch,SVC,MPU,SysTick,idle,vectortablekernel/arch/posix/hostportfortheunittests ``` ## Objects | Object | Struct | Static define | | --- | --- | --- | | Thread | `struct pbl_thread` | `PBL_THREAD_STACK_DEFINE` + `pbl_thread_create` | | Mutex | `struct pbl_mutex` | `PBL_MUTEX_DEFINE` | | Semaphore | `struct pbl_sem` | `PBL_SEM_DEFINE` | | Message queue | `struct pbl_msgq` | `PBL_MSGQ_DEFINE` | | Poll group | `struct pbl_poll_group` | `PBL_POLL_GROUP_DEFINE` | Conventions: - Every object is a caller-owned struct; there is no create/destroy pair that hands out heap handles. A `PBL_*_DEFINE` is a complete static initialiser, so a defined object is usable before the scheduler starts and needs no init call. `pbl_*_init()` exists for objects that live in dynamically allocated memory. - Blocking calls take a `pbl_timeout_t` (`PBL_NO_WAIT`, `PBL_FOREVER`, `PBL_MSEC()`, `PBL_TICKS()`). The struct type stops ticks/ms mix-ups. - Return `int`: 0 on success, `-EAGAIN` on timeout, `-EBUSY` for `PBL_NO_WAIT`. - No `_from_isr` variants: `pbl_in_isr()` picks the path and any needed context switch is requested inside the call. - Mutexes are recursive with owner and lock-site tracking. Non-recursive intent is expressed with `pbl_mutex_assert_held(m, false)`. - Priorities: higher is more urgent, `PBL_PRIO_IDLE` .. `PBL_PRIO_MAX`. - Returning from a thread entry function ends the thread. ### Threads `pbl_thread_create()` takes a `struct pbl_thread_attr`: entry, priority, privileged flag, a caller-owned stack and up to four `MpuRegion`s that are switched in with the thread. `pbl_thread.tls[]` holds the per-thread pointers the syscall layer needs. ### Introspection `debug.h` covers what core dumps, fault handling, stack checks and telemetry need: a thread walk with saved registers in the canonical order the core dump format expects, saved PC/LR/CONTROL of a blocked thread, stack bounds and high-water marks, and a run-time stats snapshot. ### Interrupts SoC interrupts are bound at build time; there is no runtime handler registration and the vector table is in flash: ``` PBL\_IRQ\_CONNECT(I2C1, 5, i2c\_irq\_handler, I2C1\_BUS, 0);PBL\_IRQ\_DIRECT(AON, 0, PBL\_IRQ\_ZERO\_LATENCY) {/\* handler body \*/} ``` A line is named after its `PBL_SOC_IRQN_` define in the SoC’s checked-in `soc_irqs.h`, which also gives its number: `PBL_IRQN(I2C1)` is `PBL_SOC_IRQN_I2C1`. The defines were generated once from the vendor `IRQn_Type`, so the names match the CMSIS `_IRQn`, and every binding asserts that the two numbers still agree. The SoC’s line count is `CONFIG_NUM_IRQS`, set in `soc/*/Kconfig.defconfig`. `PBL_IRQ_CONNECT()` calls `isr(arg)` with whatever type `isr` takes (an empty `arg` calls `isr()`); `PBL_IRQ_DIRECT()` takes the handler body instead. Binding a line twice fails to link, and a line without a `PBL_SOC_IRQN_` fails to compile. The priority is in controller units (0 is the most urgent) and is programmed for every connected line by `pbl_irq_init()` at boot, so drivers only call `pbl_irq_enable()` and `pbl_irq_disable()`. A priority more urgent than `PBL_IRQ_PRIO_MAX_SYSCALL` is a build error unless the line is flagged `PBL_IRQ_ZERO_LATENCY`, in which case the ISR must not call the kernel. An enabled line nobody connected lands in `arch_irq_spurious()`, which asserts. ### Boot The kernel owns the reset vector. `Reset_Handler()` sets the stack limit registers on cores that have them and enters `kernel_prep_c()`, which copies `.data` (and `.ramfunc` with `CONFIG_RAMFUNC`), zeroes `.bss`, calls the SoC’s `pbl_soc_early_init()` (`pbl/kernel/init.h`) for vendor system init, clocks and caches, and then calls `main()`. ### Idle The SoC tickless-idle code talks to the kernel through `pbl/kernel/idle.h`: `pbl_soc_idle()` and `pbl_soc_tick_enable()` are implemented per SoC, and `pbl_idle_confirm()`, `pbl_idle_slept()` and `pbl_kernel_tick_isr()` are what the kernel provides in return. ### Code in RAM SoCs that need code to run while flash is unavailable select `CONFIG_RAMFUNC`, which adds a `.ramfunc` output section loaded from flash and copied to RAM at boot. Code gets there in one of three ways: - a function marked `PBL_SECTION_RAM`, or a constant marked `PBL_SECTION_RAM_RODATA` (`pbl/kernel/section.h`); - whole source files, with `pbl_library_ramfunc(file.c ...)` next to the library’s `pbl_library_sources()`; - input section patterns registered against the `ramfunc` linker hook, for vendor code annotated its own way. Without `CONFIG_RAMFUNC` all three are no-ops and the code stays in flash. ## Compiler abstraction `pbl/kernel/compiler.h` is the only place the tree may spell compiler specifics: attributes (`PBL_PACKED`, `PBL_WEAK`, `PBL_NORETURN`, `PBL_SECTION()`, …) and builtins (`PBL_LIKELY()`, `PBL_UNREACHABLE()`, `PBL_CLZ()`, …). Every public macro is declared and documented once in the frontend and expands to a `*_IMPL` counterpart from the backend selected by the predefined macros: `compiler/gcc.h` for GCC and `compiler/clang.h` for Clang, which reuses the GCC definitions and blanks the attributes Clang does not implement. Supporting another compiler means adding a backend that defines the same `*_IMPL` set and a branch in the frontend; callers stay untouched. The unit-test hooks built on these (`PBL_T_STATIC`, `PBL_T_MOCKABLE`) live in `pbl/util/testing.h`. Code outside `include/pbl/kernel/compiler/` must not use ` __attribute__ ` or `__builtin_*` directly; the header is shipped with the SDK so exported headers follow the same rule. ## Configuration `kernel/Kconfig` holds the tick rate, the number of priorities, the NVIC priority levels the kernel uses, the thread limit, the TLS slot count and the stack alignment the MPU guard needs. `kernel/arch/arm/Kconfig` describes the CPU, as in Zephyr: each SoC selects its core (`CPU_CORTEX_M4`, `CPU_CORTEX_M33`, `CPU_STAR_MC1`) and features (`CPU_HAS_FPU`), the core selects its architecture (`ARMV7_M`, `ARMV8_M_MAINLINE`) and the architecture its traits (`CPU_CORTEX_M_HAS_SPLIM`). The compiler flags and the MPU backend are derived from these symbols. QEMU boards pick the emulated core with `SOC_QEMU_CORTEX_M4` or `SOC_QEMU_CORTEX_M33`. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/architecture/kernel.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/architecture/kernel.md). --- # Kernel internals Source: https://developer.repebble.com/pebbleos/docs/architecture/kernel_internals/ `kernel/` implements `include/pbl/kernel`. It replaced a FreeRTOS shim; the comparison and benchmark sections below record how the two compared while both existed. ## Layout ``` sched.creadylists,timeouts,tick,block/wake,idlethread,startthread.cthreadlifecycleandprioritiesmutex.crecursivemutexwithpriorityinheritancesem.ccountingsemaphoremsgq.cmessagequeuepoll.cpollgroupirq.cnestableinterruptlockdebug.cthreadwalk,savedregisters,stackandrun-timestatskernel.htheinterfacebetweentheobjects,theschedulerandthearcharch/arch.hwhatanarchmustprovidearch/arm/Cortex-Mport:contextswitch,SVC,MPU,SysTick,idlearch/posix/hostport:threadsarepthreads,timeisvirtual;fortestskernel\_test.hharnessentrypointsusedbytests/kernel ``` ## Design **Scheduling.** Fixed priorities, `CONFIG_KERNEL_NUM_PRIORITIES` of them, one FIFO ready list per priority and a bitmap picked with `clz`, so choosing the next thread is O(1). Threads of equal priority round-robin on every tick (as FreeRTOS does with time slicing) and on `pbl_thread_yield()`. The running thread stays on its ready list; blocking removes it. **Blocking.** Every object owns a `struct pbl_waitq`: a singly linked list ordered by priority, FIFO within a priority. `sched_block()` moves the current thread onto the object’s wait queue and the timeout list, requests a switch and drops the interrupt lock; it returns with the lock re-taken and the wake code. Semaphores and mutexes hand their token straight to the woken thread, so a wake is never spurious and a lower-priority giver cannot steal it back. Queues and poll groups wake one waiter and let it re-check. **Timeouts.** One list sorted by absolute wake tick, wrap-safe compare. The tick expires the head, sleeps use the same list, and the idle thread asks it how long it may sleep. Insertion is O(n) in the number of pending timeouts, which on this firmware is a handful. **Priority inheritance.** Single level, the same as the FreeRTOS fork’s light mutex: a waiter raises the owner to its own priority; the owner drops back to its base priority when it holds no mutex any more. A boosted thread is re-sorted in its ready list or wait queue. **Interrupts.** `pbl_irq_lock()` raises BASEPRI to `CONFIG_KERNEL_IRQ_PRIO_MAX_SYSCALL` with a nesting counter, exactly the FreeRTOS critical section. ISRs above that priority may not call the kernel. Object operations run under that lock; there is no separate scheduler lock inside the kernel. `pbl_sched_lock()` defers switches until unlocked. **Idle.** The idle thread yields to other threads at its priority, then calls the arch idle hook with the ticks until the next timeout. On Cortex-M that forwards to the SoC’s `pbl_soc_idle()` when the gap is at least two ticks, and the SoC reports the sleep back with `pbl_idle_slept()`, which advances the clock and expires timeouts. **Threads.** `struct pbl_thread` is caller-owned; the backend state lives in its `backend` member, first in the struct so the arch code can find the saved stack pointer at offset zero. Stacks are filled with a pattern at creation for the high-water mark. Returning from the entry function ends the thread. A thread suspended while blocked sees the blocking call fail with `-EINTR` when resumed; the process manager only suspends threads it is about to kill or resumes immediately. **No allocation.** Nothing in the backend calls an allocator; `PBL_*_DEFINE` initialisers are complete and `pbl_kernel_init()` returns at once. ## Cortex-M arch The saved context keeps the layout of the FreeRTOS port: CONTROL, r4-r11, PSPLIM on ARMv8-M, EXC\_RETURN, then the hardware frame, with s16-s31 in between when the FP context is active. Core dumps and the fault handler read that layout unchanged. PendSV saves the context, picks the next thread under BASEPRI, rewrites the four per-thread MPU regions through the RBAR alias registers and restores. Supervisor calls keep the port’s numbers: 0 starts the scheduler, 1 yields, 2 raises privilege. The privilege-raise path copies the exception frame onto the thread’s dedicated syscall stack when `pbl_kernel_syscall_stack()` provides one and hands the caller’s stack pointer and stacked return slot to `pbl_kernel_syscall_entered()`, which is what the firmware’s syscall island expects. The vector table (`kernel/arch/arm/vector_table.c`) is a constant array in `.isr_vector`. `PBL_LISTIFY()` expands its `CONFIG_NUM_IRQS` slots to `pbl_isr_`, each a weak alias of `arch_irq_spurious()` that `PBL_IRQ_CONNECT()` or `PBL_IRQ_DIRECT()` overrides with a strong definition, `n` being the line’s `PBL_SOC_IRQN_`. Each binding also drops a `{irq, prio}` record into the `.pbl_irq_prio` section, which `pbl_irq_init()` walks to program the NVIC after pointing VTOR at the table. A weak default never pulls an object out of a static library, so a binding inside a `stlib` library only takes effect when something else in the same object is referenced. The port defines `pbl_cur` in `.kernel_unpriv_ro_bss`, the firmware’s unprivileged-readable kernel data, because `pbl_thread_current()` and `pebble_task_get_current()` run unprivileged inside apps. ## POSIX arch and tests Each kernel thread is a pthread; exactly one runs at a time, and control passes when the kernel would switch. Interrupts are simulated: a test wraps calls in `pbl_test_isr_enter()`/`exit()` and delivers ticks with `pbl_test_tick()`. When every thread is blocked, the idle hook jumps the clock to the next timeout. This makes the scheduling decisions exactly reproducible, so `tests/kernel/test_kernel.c` can assert trace strings such as “lhLHMd” for the priority-inheritance scenario. ## Compared with other kernels | | FreeRTOS (fork) | Zephyr | NuttX | ThreadX | Native | | --- | --- | --- | --- | --- | --- | | Ready queue | list per priority, `clz` optional | dumb list / scalable / multiq | list per priority | bitmap + list | bitmap + list | | Time slicing | per tick | configurable | configurable | per thread | per tick | | Timeouts | two delayed lists | single delta list | wdog list | per-timer wheel | single absolute list | | Wait queues | priority ordered | priority ordered | priority ordered | FIFO or priority | priority ordered | | Mutex PI | single level | single level | multi level | single level | single level | | Sem/mutex wake | wake, retry | handoff | handoff | handoff | handoff | | Static objects | since v9 | yes | no | yes | yes | | Idle | idle task hook | `k_cpu_idle` + tickless | tickless | tickless | idle thread + SoC hook | | Stack overflow | pattern / MPU guard | MPU guard / canary | canary | analysis | PSPLIM (v8-M), MPU guard, pattern | | Thread exit | must not return | returns | returns | returns | returns | What was taken from where: - Direct handoff on semaphores and mutexes (Zephyr, ThreadX). Removes the give-then-retry race and the spurious wakeups the fork’s queue-based semaphores have. - Priority-ordered wait queues with FIFO within a priority (everyone). - Completed static initialisers, so nothing has to run before objects are usable (Zephyr’s `K_*_DEFINE`). - A poll group that scans members from a rotating cursor (Zephyr’s `k_poll` fairness), and no rule about draining members only through the group, which the FreeRTOS queue set imposes and which this firmware’s event loop tiptoes around. - Threads may return from their entry function (everyone but FreeRTOS). - Saved registers of the running thread are read from its live exception frame when asked from an ISR, so the task watchdog reports the real stuck PC instead of a stale context (a FreeRTOS limitation here). - Thread struct reuse is checked: creating a thread in a struct whose previous thread is still alive asserts. Not done, on purpose or for later: - Multi-level priority inheritance (NuttX). The firmware never nests locks deeply enough for it to matter, and the fork did not have it either. - Timer wheels (ThreadX) or delta lists (Zephyr) for timeouts. The pending timeout count here is small; an O(n) insert keeps the code obvious. - Run-time statistics in cycles from the DWT counter instead of ticks. QEMU has no DWT, so it is left for a board with one. - Per-thread preemption threshold (ThreadX) and memory domains (Zephyr). The MPU regions stay per thread as the firmware defines them today. - Multicore. Everything assumes one CPU. ## Benchmarks The numbers below come from a throwaway micro-benchmark harness that ran in place of KernelMain on QEMU; it is not kept in the tree. Times came from the tick counter refined with the SysTick down-counter, so they are QEMU virtual time: only the relative numbers between the two backends on the same board mean anything. Results in ns per iteration (2026-09-02, `-Os`, all boot services still running in the background): | Benchmark | gabbro shim | gabbro native | flint shim | flint native | | --- | --- | --- | --- | --- | | irq\_lock\_unlock | 151 | 142 | 82 | 102 | | mutex\_lock\_unlock (uncontended) | 927 | 413 | 610 | 283 | | sem\_give\_take (no wait) | 658 | 347 | 398 | 247 | | msgq\_put\_get (4 bytes, no wait) | 723 | 602 | 458 | 341 | | yield\_switch (two equal-priority threads) | 10869 | 11323 | 10756 | 10588 | | sem\_pingpong (round trip, two switches) | 28647 | 23715 | 26421 | 21401 | | msgq\_pingpong (round trip) | 30805 | 25265 | 26433 | 22376 | | poll\_wake (three-queue group, one wake) | 29109 | 24520 | 25681 | 23205 | | irq\_to\_thread (pended IRQ to waiting thread) | 2000 | 1620 | 1384 | 1244 | | thread\_create\_exit | 33149 | 24441 | 29712 | 21871 | | sleep\_1\_tick | 997605 | 1001495 | 998345 | 996250 | Reading the numbers: - Uncontended primitives are 1.2x to 2.2x faster natively. The shim pays for the FreeRTOS queue machinery plus the wrapper, the native objects are a counter or a byte ring with an owner pointer. - Anything that context switches is dominated by PendSV plus the MPU reprogramming and the QEMU cost of the exception itself, so the two backends land within 20% of each other. `yield_switch` is the same code path in both and shows the noise floor of the method: about 5%. - `sleep_1_tick` is a sanity check that the tick source runs at `CONFIG_KERNEL_TICK_HZ`. - `thread_create_exit` sleeps a tick every 16 iterations (excluded from the measurement) so that FreeRTOS’s idle task can reclaim deleted TCBs; the kernel needs no reclamation since the thread object is static. Code size of the kernel library alone (`arm-none-eabi-size`, `-Os`): | Board | FreeRTOS shim + FreeRTOS | Native | | --- | --- | --- | | qemu\_gabbro (Cortex-M33) | 12541 text / 57 data / 744 bss | 4725 text / 0 data / 1218 bss | | qemu\_flint (Cortex-M4) | 12403 text / 57 data / 744 bss | 4725 text / 0 data / 1218 bss | The native bss is mostly the 1 KiB idle stack; the shim’s idle task stack comes out of the FreeRTOS heap instead. ## Verification - `tests/kernel/test_kernel`: 18 host tests on the POSIX arch. - qemu\_gabbro and qemu\_flint: boot to the watchface, launch an app from the launcher and back out, a gdb breakpoint sweep on every assert and fault entry point, and the benchmark suite. Not yet run on real hardware. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/architecture/kernel\_internals.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/architecture/kernel_internals.md). --- # Shell Source: https://developer.repebble.com/pebbleos/docs/architecture/shell/ `subsys/shell/` is the debug command shell. Its public API is `include/pbl/shell/shell.h`, and backends implement `include/pbl/shell/backend.h`. It is modelled on the Zephyr shell: commands are defined next to the code they drive, collected at link time, and served by any number of shell instances, one per backend. ## Commands A command is a `struct pbl_shell_cmd`: a name, a help string, a handler and optionally a table of subcommands, so commands form a tree (`bt disc start`). - `PBL_SHELL_CMD_REGISTER()` / `PBL_SHELL_CMD_ARG_REGISTER()` register a root command from any file. The entries land in the `.pbl_shell_root_cmds` linker section, sorted by name. - A file-local subcommand table is a plain `struct pbl_shell_cmd` array of `PBL_SHELL_CMD()` entries ending with `PBL_SHELL_SUBCMD_SET_END`. - `PBL_SHELL_SUBCMD_SET_CREATE()` builds a subcommand set that other files extend with `PBL_SHELL_SUBCMD_ADD()`, without referencing any symbol of the owner. The entries live in `.pbl_shell_subcmds..` input sections, sorted between a start marker and a terminator, so a set is a contiguous, NULL-terminated array once linked. This is how, for instance, the Bluetooth, console and driver code all add to `bt` or `flash`. Handlers get `argc`/`argv` with `argv[0]` being the matched command name. `mandatory` (which counts `argv[0]`) and `optional` let the core check the argument count; `-h`/`--help`, unknown subcommands and commands without a handler print the help of the matched node. A handler returns 0 or a negative errno; `-EINPROGRESS` keeps the shell busy until `pbl_shell_cmd_done()`, for commands that finish from a callback. Shell code is wrapped in `#ifdef CONFIG_SHELL`, and modules with a group of commands can gate them further; for example `drivers/imu/shell.c` builds with `CONFIG_IMU_SHELL`. ``` static int prv\_accel\_read(const struct pbl\_shell \*sh, size\_t argc, char \*\*argv) {...pbl\_shell\_print(sh, "x=%d y=%d z=%d mg", sample.x, sample.y, sample.z);return 0;}static const struct pbl\_shell\_cmd sub\_accel[] = {PBL\_SHELL\_CMD(read, NULL, "Read one sample", prv\_accel\_read),PBL\_SHELL\_SUBCMD\_SET\_END,};PBL\_SHELL\_CMD\_REGISTER(accel, sub\_accel, "Accelerometer", NULL); ``` ## Instances and backends `PBL_SHELL_DEFINE()` creates an instance bound to a backend. Every instance has its own line buffer and state, and commands receive the instance they run on, so their output goes back where the command came from. Commands of every instance run on KernelBG. - **Interactive** instances (with a prompt) feed received characters with `pbl_shell_input_from_isr()`. The core does line editing, echo, the prompt and tab completion of command names on KernelBG. - **Line** instances (no prompt) submit whole command lines with `pbl_shell_execute_line()`, and get a `done` callback when the command finishes. The firmware has two backends, both in `fw/console/`: - `shell_dbgserial.c`: interactive, on the debug serial. Ctrl-C switches the serial port from logs to the shell, Ctrl-D back to logs. - `shell_pulse.c`: line-based, on the PULSE prompt protocol, which is what `pbl console` talks to. Each output line is one prompt message. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/architecture/shell.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/architecture/shell.md). --- # Task watchdog Source: https://developer.repebble.com/pebbleos/docs/architecture/task_watchdog/ `subsys/task_wdt/` is the software watchdog that catches stuck threads and turns them into core dumps. Its public API is `include/pbl/task_wdt/task_wdt.h`. It is modelled on the Zephyr task watchdog: a fixed pool of channels, each with its own timeout and optional expiry callback, backing a single hardware watchdog (`include/pbl/drivers/watchdog.h`). ## Model - A **channel** watches one thread. `pbl_task_wdt_add()` binds it to the calling thread (or an explicit one) with a timeout (the system threads use `CONFIG_TASK_WDT_TIMEOUT_MS`); the thread, or something acting on its behalf, must call `pbl_task_wdt_feed()`, `pbl_task_wdt_feed_self()` or `pbl_task_wdt_feed_thread()` before the timeout runs out. `pbl_task_wdt_feed_all()` exists for long, lock-holding operations such as flash erases. - A dedicated **watchdog thread** at the highest priority wakes every `CONFIG_TASK_WDT_CHECK_PERIOD_MS`, compares every channel against the kernel uptime, and feeds the hardware watchdog when it decides not to reset. Its sleep also bounds how long the SoC may stay in deep sleep, so the hardware watchdog is kept alive without SoC-specific timers. A thread spinning with interrupts disabled, or an interrupt handler that never returns, starves the watchdog thread too; the hardware watchdog is the backstop for those. - When a channel **expires** , the watchdog thread logs the stuck thread’s name, saved PC and LR, stores them in the reboot reason (`RebootReasonCode_Watchdog`, with the fed and active channel masks in `data8`), and calls the channel callback. The callback may try to unblock the thread and returns a pointer naming the work it was doing, which lands in `stuck_task_callback`. It runs again on every check while the channel stays expired. - After `CONFIG_TASK_WDT_GRACE_MS` of a channel staying expired, counted from the check that first found it expired, the system resets through `reset_due_to_software_failure()`, so a core dump is written. That check never resets, so the callback always gets the grace period to recover the thread, and the next check runs once the grace period is over instead of a full check period later. Without `CONFIG_WATCHDOG` it logs instead and keeps running. A channel fed again within the grace period clears the reboot reason and logs the recovery. - `pbl_task_wdt_suspend()` keeps every channel fed for a bounded time (or until `pbl_task_wdt_resume()`), for phases such as boot or a long settings-file compaction where stalls are expected. ## Users - `fw/main.c` starts the watchdog, suspends it for the first 30 s of boot, adds the NewTimers channel (fed from a regular timer, which proves the timer thread still runs callbacks) and the KernelMain channel, which the launcher event loop feeds. - `fw/services/system_task/service.c` owns the KernelBackground channel. Its callback throttles the app thread to the idle priority for a short time when the system task is ready to run but starved. - `fw/console/pulse2.c` owns the PULSE channel. - Flash, filesystem and console code feeds the calling thread’s channel from loops that legitimately run for seconds. ## Tests `tests/subsys/task_wdt/` runs the subsystem on the POSIX kernel port with virtual time; see `docs/development/testing.md` for how to run it. The QEMU boards carry a real hardware watchdog (the CMSDK APB watchdog, driven by `drivers/watchdog/qemu.c`), so both paths can be tried in the emulator: `wdt stall main` ends in a core dump, and `wdt stall irq` ends in a hardware reset that the next boot reports as a watchdog reset. Generated from PebbleOS [63769a1e6](https://github.com/coredevices/pebbleos/commit/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee). This page is maintained in the pebbleos repository: [docs/architecture/task\_watchdog.md](https://github.com/coredevices/pebbleos/blob/63769a1e68e738ea37fd2826d3eeeea4fa25a7ee/docs/architecture/task_watchdog.md). --- # Contributing to the Mobile App Source: https://developer.repebble.com/mobile-app/contributing/ The Pebble mobile app is developed at [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp). The public repository is a copy of the internal one that Core Devices works in, synced by hand, so it can lag behind. Changes arrive as pull requests. ## Sending a Change - Fork the repository and make the change on a branch. - Open a pull request against `main`. Say in the description what the change does and how you tested it. - If you used AI to write any of the code, say so in the description. The maintainers do not accept changes that the author has not read and understood. - Contributions to `libpebble3` need the [Contributor License Agreement](https://cla-assistant.io/coredevices/libpebble3). There is no sign-off requirement on commits. Core Devices has a small mobile team, and reviewing a change can mean testing it with hardware and maintaining it afterwards, so a review can take some time. A pull request that adds a feature the team has not planned is more likely to wait than a fix. Bugs in the app are reported from the app itself, under _Settings_, _Get Help_, _New Bug Report_, rather than as GitHub issues. The in-app report attaches the logs the team needs. ## Forks Maintaining a fork is fine. A fork may not use "Pebble" in its name except to refer to the watch, so "Awesome App for Pebble" is acceptable and "Pebble Awesome" is not. The repository README has the details. ## License The app is dual-licensed by Core Devices LLC. It can be used under the [GNU GPLv3](https://github.com/coredevices/mobileapp/blob/main/LICENSE), or under a paid [commercial license](https://github.com/coredevices/mobileapp/blob/main/LICENSE-COMMERCIAL) agreed with Core Devices. The GPL grant carries an additional permission to distribute builds through the Apple App Store. --- # Mobile App Source: https://developer.repebble.com/mobile-app/ The Pebble mobile app is the phone app for Pebble watches and the Index 01 ring. It runs on iOS and Android and is available at [repebble.com/app](https://repebble.com/app). The app is open source at [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp), written in Kotlin Multiplatform with one codebase for both platforms. Developers can build their own features and open a pull request to the repository, or maintain their own fork. For app developers, the mobile app is the other half of a watchapp. It runs the PebbleKit JS part of an app, carries the [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) that the `pebble` tool uses to install and debug apps on a watch, sideloads `.pbw` files, and talks to the Pebble appstore. For Index 01 it collects recordings from the ring, sends them to [webhooks](https://developer.repebble.com/index-01/webhooks.md), connects to [MCP servers](https://developer.repebble.com/index-01/mcp.md) and runs [_plugins_](https://developer.repebble.com/guides/plugins.md). ## Building the App The repository [README](https://github.com/coredevices/mobileapp#readme) has the setup steps. On Android the app builds with Gradle from Android Studio and needs a `google-services.json` in `androidApp/src`; a dummy file is provided. On iOS the build needs Java 17, CocoaPods, an Android SDK (several modules use the Android Gradle plugin) and the iOS platform for Xcode, after which `./gradlew podInstall` sets up the dependencies and the app is built from `iosApp/iosApp.xcworkspace` in Xcode. Features such as bug reporting, Google sign-in and online transcription need tokens in `gradle.properties`; the core features work without them. The app can connect to a PebbleOS emulator over TCP in place of a watch. [_Testing with the Pebble Mobile App_](https://developer.repebble.com/guides/tools-and-resources/testing-with-the-mobile-app.md) covers running the app against the emulator, which is how a change that touches both the firmware and the app is tested. ## Related - [_PebbleKit JS_](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) has a section on the differences between the PebbleKit JS runtime in the Pebble mobile app and the original one. - [_Developer Connection_](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) describes how the `pebble` tool installs apps through the phone. - [_Plugins (Preview)_](https://developer.repebble.com/guides/plugins.md) describes the `.pbw` packages that run inside the app and extend the Index agent. - [Index 01 webhooks](https://developer.repebble.com/index-01/webhooks.md) describes the HTTP request the app sends for each recording. - [Contributing](https://developer.repebble.com/mobile-app/contributing.md) describes how changes get into the repository. --- # examples Source: https://developer.repebble.com/examples/ There are no examples that match the filters on the right. ![](https://raw.githubusercontent.com/JavierRizzoA/Pebble-Mercury/master/img/screenshots/emery1.png) ## Mercury Watchface · Graphics · Community A stylish analog watchface with a background gradient that follows the minutes hand. Supports all Pebble platforms with platform-appropriate rendering. Created for Rebble Hackathon 002. [GitHub](https://github.com/JavierRizzoA/Pebble-Mercury/) ![](https://developer.repebble.com/assets/images/examples/halcyon.png) ## Halcyon Watchface · Color · Community An analog-digital watchface featuring a 24-hour solar day ring that highlights sunrise, sunset, and sun position. Includes 10 color presets, extensive color customization, and on-device sunrise/sunset calculation. Created for Rebble Hackathon 002. [GitHub](https://github.com/freakified/halcyon/) ![](https://developer.repebble.com/assets/images/examples/timestyle.png) ## TimeStyle Watchface · Configuration · Community One of the most popular Pebble watchfaces, with 200+ stars and 16 contributors. Features antialiased text, 20+ color presets, extensive complications (weather, steps, sleep, battery, world clock), and support for 30 languages. [GitHub](https://github.com/freakified/TimeStylePebble/) ![](https://developer.repebble.com/assets/images/examples/hellopoco-pdc-process.png) ## Poco PDC Process Alloy · Poco · Graphics Cycles through five PDC vector images, cloning each one and changing its stroke and fill colors with the draw command list `process()` method before drawing it with Poco. [GitHub](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-pdc-process/) ![](https://developer.repebble.com/assets/images/examples/yoyogi.png) ## Yoyogi Alloy · Watchface · Piu Analog watchface built with the Piu UI framework. Draws the hands and their shadows from PDC vector images over a bitmap dial, and handles display size changes. [GitHub](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/yoyogi/) ![](https://developer.repebble.com/assets/images/examples/hellopiu-balls.gif) ## Piu Balls Alloy · Piu · Animation The classic Moddable SDK bouncing balls example, ported to Pebble. A fun demonstration of the Piu UI framework's animation capabilities with 1-bit ball graphics. [GitHub](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopiu-balls/) ![](https://developer.repebble.com/assets/images/examples/cupertino.png) ## Cupertino Alloy · Watchface · Piu The classic macOS watch cursor as a watchface. Built with the Piu UI framework, demonstrating SVG-based clock hands and bitmap backgrounds. [GitHub](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/cupertino/) ![](https://developer.repebble.com/assets/images/examples/redmond.png) ## Redmond Alloy · Watchface · Piu The original Windows clock, now on your wrist. Demonstrates using different graphical assets for color and black-and-white watches within the same Piu application. [GitHub](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/redmond/) ![](https://developer.repebble.com/assets/images/examples/zurich.png) ## Zurich Alloy · Watchface · Piu The iconic Swiss railway station clock, now on your wrist. Built with the Piu UI framework using bitmaps and PDC (SVG) vector images for the rotating clock hands. [GitHub](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/piu/watchfaces/zurich/) ![](https://developer.repebble.com/assets/images/examples/poco-qrcode.png) ## Poco QR Code Alloy · Poco · Graphics Dynamically generates and displays a QR code using the Poco lightweight graphics renderer. Shows how to use Poco for pixel-precise rendering on Pebble. [GitHub](https://github.com/Moddable-OpenSource/pebble-examples/tree/main/hellopoco-qrcode/) ![](https://github.com/pebble-hacks/pandas-and-bananas/raw/master/screenshots/screenshot.png) ## Pandas and Bananas Game · Graphics · Animation · Color Color game where the player controls a panda via accelerometer tilting or buttons to catch fruit and avoid bombs. A fun showcase of accelerometer input and sprite animation. [GitHub](https://github.com/pebble-hacks/pandas-and-bananas/) ![](https://github.com/pebble-hacks/block-world/raw/master/screenshots/screenshot.png) ## Block World Game · Graphics · Animation · Color A Minecraft-inspired block-builder game using isometric 3D rendering. Place blocks in a 16x16x14 grid using the PGE game engine and isometric rendering libraries. [GitHub](https://github.com/pebble-hacks/block-world/) ![](https://raw.githubusercontent.com/pebble-examples/simple-analog/master/simple-analog-screenshot.png) ## Simple Analog Watchface · Analog A clean analog watchface using drawn clock hands. A great starting point for building your own analog watchface. [GitHub](https://github.com/pebble-examples/simple-analog/) ![](https://raw.githubusercontent.com/pebble-examples/ks-clock-face/master/screenshots/screenshot-color.png) ## KS Clock Face Watchface · Animation · Color The animated color watchface from the Pebble Time Kickstarter video. Expands from a dot, sweeping the clock arms outward. Auto-detects color capability for black-and-white or color rendering. [GitHub](https://github.com/pebble-examples/ks-clock-face/) ![](https://raw.githubusercontent.com/pebble-examples/ui-patterns/master/screenshots/dialog-choice.png) ## UI Patterns Design · UI Reference implementations of recommended Pebble UI design patterns, including checkbox lists, radio buttons, dialogs, PIN entry, progress bars, and text animations. [GitHub](https://github.com/pebble-examples/ui-patterns/) ![](https://raw.githubusercontent.com/pebble-examples/cards-example/master/screenshots/screenshot1.gif) ## Weather Cards Example Graphics · Color · Animation Demonstrates the Pebble Draw Commands API with a cards-based weather app. Shows how to draw and animate vector graphics, including SVG to PDC conversion. [GitHub](https://github.com/pebble-examples/cards-example/) ![](https://developer.repebble.com/assets/images/tutorials/alloy-watchface-tutorial/part6~emery.png) ## Alloy Watchface Tutorial Tutorial · Watchface The finished code for each part of the Alloy watchface tutorial, written in JavaScript: time and date, custom fonts, battery and connection indicators, weather with fetch(), Timeline Peek and user settings. [GitHub](https://github.com/coredevices/alloy-watchface-tutorial/) ![](https://developer.repebble.com/assets/images/tutorials/watchface-tutorial/part6~emery.png) ## C Watchface Tutorial Tutorial · Watchface The finished code for each part of the C watchface tutorial: time and date, custom fonts, battery and Bluetooth indicators, weather from PebbleKit JS, Timeline Peek and a Clay settings page. [GitHub](https://github.com/coredevices/c-watchface-tutorial/) ![](https://developer.repebble.com/assets/images/examples/compass.png) ## Pebble Compass Sensors · Animation · Color Full-featured compass app with smooth transitions between rose and band views, heading inertia physics, and a calibration dialog. An excellent reference for the compass and accelerometer APIs. [GitHub](https://github.com/pebble-hacks/pebble-compass/) ![](https://raw.githubusercontent.com/pebble-examples/classio-battery-connection/master/classio-battery-connection-screenshot.png) ## Classio Battery Connection Watchface · Battery Service · Bluetooth Service Digital watchface showing time with seconds, battery level indicator, and Bluetooth connection status. A useful reference for the Battery and Bluetooth service APIs. [GitHub](https://github.com/pebble-examples/classio-battery-connection/) ![](https://github.com/pebble-hacks/isotime/raw/master/screenshots/screenshot.png) ## Isotime Watchface · Graphics · Animation · Color Striking isometric 3D watchface that displays time digits built from individual block segments. Features staggered animation of segments that change each minute, sleeping completely between animations. [GitHub](https://github.com/pebble-hacks/isotime/) ![](https://raw.githubusercontent.com/pebble-examples/time-dots/master/screenshot.png) ## Time Dots Watchface · Color Watchface for Pebble Time Round showing hours and minutes as a bar and dots. Designed specifically for the round display. [GitHub](https://github.com/pebble-examples/time-dots/) ![](https://raw.githubusercontent.com/pebble-examples/pebble-faces/11305757db5fa9ce5e448aa0ba4e7beb8a38b020/screenshots/screenshot.png) ## Pebble Faces Images · PebbleKit Downloads, decompresses, and displays PNG images from the Internet using PebbleKit JS for networking. A great example of image handling and phone communication. [GitHub](https://github.com/pebble-examples/pebble-faces/) ![](https://raw.githubusercontent.com/pebble-examples/concentricity/master/screenshots/concentricity~chalk.png) ## Concentricity Watchface · Graphics · Color Watchface showing hours, minutes, and seconds as concentric rings around the center of the display. Supports aplite, basalt, and chalk with platform-appropriate rendering. [GitHub](https://github.com/pebble-examples/concentricity/) --- # Index 01 Source: https://developer.repebble.com/index-01/ Index 01 is a small ring with a button and a microphone, sold at [repebble.com/index](https://repebble.com/index). Press the button, say the thought you want to keep, and the recording is sent to your phone, where it is added to your notes, set as a reminder, or kept in the Index feed for later. The ring records only while the button is pressed. Speech recognition and the assistant can run on the phone with no internet connection, or in the cloud. The ring sends each recording to the Pebble mobile app over Bluetooth, and the app transcribes it, acts on it, and keeps it in the Index feed. Developers can receive each recording at their own HTTP endpoint with a [webhook](https://developer.repebble.com/index-01/webhooks.md), give the assistant new tools by connecting an [MCP server](https://developer.repebble.com/index-01/mcp.md), or write a [plugin](https://developer.repebble.com/guides/plugins.md) that the assistant can use. ## How the Ring and the App Split the Work The ring firmware is a small fixed loop: it detects button presses, records audio while the button is held, holds recordings until the phone collects them, and transfers them over Bluetooth. It does not transcribe or interpret anything. All of the logic lives in the Pebble mobile app, which is open source at [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp). The ring code is in the `experimental` module of that repository. This section documents the app side only; there is no ring firmware SDK. ## Gestures The button recognises five gestures. Each one is routed from the _Ring Button_ screen in the Index 01 settings of the Pebble mobile app. The three music gestures can be configured on Android only; on iOS they are shown disabled. | Gesture | Options | | --- | --- | | Click, Double click, Triple click | Play/Pause, Next track, or Nothing | | Hold & Talk | Index agent, Web search, MCP sandbox, Webhook only, or Nothing | | Double click & hold | Index agent, Web search, MCP sandbox, Webhook only, or Nothing | The two hold gestures record a voice note. _Index agent_ transcribes the note and takes actions such as creating a note, a reminder, a timer, or a calendar event. _Web search_ answers a question and puts the answer in the feed. _MCP sandbox_ runs the recording through a sandbox group of MCP servers that you choose. _Webhook only_ sends the recording to your endpoint and does nothing else. A recording gesture routed to _Index agent_, _Web search_ or _MCP sandbox_ can also send a copy to its webhook with the _Also send to webhook_ switch; the switch is not shown for _Webhook only_ or _Nothing_. ## Local and Cloud Processing Speech recognition and the assistant model are each set to run in the cloud or on the phone in the Index 01 settings. Webhooks do not depend on either setting: the audio is sent as recorded, and the transcript is whatever the selected speech recognition produced. Remote MCP servers are only called by the cloud model. ## Building on Index 01 There are three extension points. - [Plugins](https://developer.repebble.com/guides/plugins.md) run inside the Pebble mobile app and declare actions and data sources. The assistant will call a plugin's actions as tools, and a plugin that declares `create_note` or `create_reminder` can be offered as a destination for notes and reminders. Plugins are a preview and are off by default. - [Webhooks](https://developer.repebble.com/index-01/webhooks.md) send each recording as a multipart HTTP POST to a URL you configure, with the audio, the transcript, or both, and an optional HMAC-SHA256 signature. Use this to feed recordings into your own service, an automation tool, or a note-taking app. You run the endpoint. - [MCP servers](https://developer.repebble.com/index-01/mcp.md) add tools to the assistant. The app connects to any MCP server over HTTP, lists its tools, and lets the Cloud LLM call them when it processes a recording. You run the server. ## Where the Code Lives Everything in this section is implemented in the Pebble mobile app at [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp): - Webhook request format and signing: [`external/indexwebhook/`](https://github.com/coredevices/mobileapp/tree/main/experimental/src/commonMain/kotlin/coredevices/ring/external/indexwebhook/) - Gesture routing: [`service/button/`](https://github.com/coredevices/mobileapp/tree/main/experimental/src/commonMain/kotlin/coredevices/ring/service/button/) - MCP settings screens: [`settings/mcp/`](https://github.com/coredevices/mobileapp/tree/main/experimental/src/commonMain/kotlin/coredevices/ring/ui/screens/settings/mcp/) - MCP client: [`mcp/client/`](https://github.com/coredevices/mobileapp/tree/main/mcp/src/commonMain/kotlin/coredevices/mcp/client/) --- # MCP Servers Source: https://developer.repebble.com/index-01/mcp/ The Index 01 assistant calls tools through the [Model Context Protocol](https://modelcontextprotocol.io). The built-in actions (notes, reminders, timers, calendar, messaging) are MCP servers inside the Pebble mobile app. You can add your own server over HTTP, and the assistant lists its tools and calls them when a recording asks for something they do. The MCP client is in `mcp/src/commonMain/kotlin/coredevices/mcp/client/` and the settings screens are in `experimental/src/commonMain/kotlin/coredevices/ring/ui/screens/settings/mcp/` of [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp). ## Requirements - The server speaks MCP over HTTP with either the SSE transport or the Streamable HTTP transport. Pick the matching one when you add the server. - The URL is reachable from the phone. Use HTTPS unless the server is on the same local network as the phone. - Authentication, if any, is a single static `Authorization` header value. OAuth flows are not supported. - `tools/list` returns every tool in one page. The app does not follow `nextCursor`. - Prompts are optional. Only prompts without arguments are offered, and only the text of the first message of a prompt is used. - Servers are called by a cloud model. See [Cloud LLM Only](#cloud-llm-only). ## Adding a Server In the Pebble mobile app: - Open the Index 01 settings and tap _MCP & Tool Settings_. - Open the _MCP Servers_ tab and tap _Add MCP Server_. - Enter a _Name_. Letters, digits, `-` and `_` only, up to 32 characters. The name identifies the server in the app and in tool names. - Enter the _URL_. The app connects as you type, shows the server's title under the field when the connection works, and shows the error when it does not. A server that cannot be reached can still be saved. - Choose _SSE_ or _Streamable_ to match the server's transport. _SSE_ is the default. - Under _Groups_, pick the sandbox groups the server belongs to. See [Sandbox Groups](#sandbox-groups). - To authenticate, expand _Authorization (optional)_ and enter the full header value, for example `Bearer token123`. - If the server offers prompts, expand _Prompts_ and select the ones to include. - Tap _Save_. Edit a server by tapping it in the list. _Delete_ removes it from every group. ### The Authorization Header The value you enter is sent as the `Authorization` header on every request the app makes to the server, including the connection check in the dialog, as entered. Include the scheme, so `Bearer ` rather than the bare token. The app does not refresh or rotate it. Leave the field empty for a server without authentication. ### Prompts and Instructions The `instructions` string a server returns from `initialize` is added to the assistant's context whenever the server is in the active group. Prompts you select in the dialog are fetched with `prompts/get` and appended after the instructions. Use them for standing guidance on when and how to call the server's tools. ### Caching The app caches `tools/list` and `prompts/list` results. If the server declares `listChanged` for tools or prompts, the cache lasts one hour and is cleared when the server sends the corresponding `notifications/*/list_changed` notification. Otherwise it lasts five minutes. ## Sandbox Groups A sandbox group is a set of MCP servers and the model that runs them. The _Groups_ tab in _MCP & Tool Settings_ lists them. Every phone has a group named _Default MCP Sandbox_ that runs the Index agent with the built-in actions; servers you add to it are available to a gesture routed to _Index agent_. You can also create your own groups and route a recording gesture to one by choosing _MCP sandbox_ on the _Ring Button_ screen. Each group has a _Model Type_: | Model type | Behaviour | | --- | --- | | Index Agent | The default Index assistant. Understands notes and the built-in actions. Cannot be customised. | | Default | Fast at intent recognition. Fine for most tool sets. | | High Capability | Slower. Better with large numbers of tools and more complex tasks. | Deleting a group does not delete its servers; they can be added to other groups. ## Cloud LLM Only Remote MCP servers are always called by a cloud model. The _Agent Model_ setting in the Index 01 settings applies to the Index agent, which is what runs the _Default MCP Sandbox_ group and any group whose model type is _Index Agent_: - With _Cloud LLM_ or _Cloud LLM (with Local fallback)_, HTTP servers in the default group are available to the Index agent. - With _Local LLM_, HTTP servers in the default group are shown as disabled with the reason "Remote MCP servers need the Cloud LLM". The on-device model also does not support every built-in action. - The _Local LLM_ options cannot be selected while the default group's model type is _Default_ or _High Capability_. Changing the default group to one of those model types switches _Agent Model_ back to _Cloud LLM_. Groups with the _Default_ or _High Capability_ model type run in the cloud whatever _Agent Model_ is set to, and a gesture can be routed to them under any setting. They need a signed-in Pebble account. --- # Webhooks Source: https://developer.repebble.com/index-01/webhooks/ **Notice** This page is generated from [`INDEX_WEBHOOK_API.md`](https://github.com/coredevices/mobileapp/blob/ec2156cc38a850e5458addba2a2fb8efa34396df/experimental/src/commonMain/kotlin/coredevices/ring/external/indexwebhook/INDEX_WEBHOOK_API.md) in the [Pebble mobile app repository](https://github.com/coredevices/mobileapp) at commit [ec2156cc3](https://github.com/coredevices/mobileapp/commit/ec2156cc38a850e5458addba2a2fb8efa34396df). Relative paths refer to that directory. Changes belong in that repository. Send Index ring recording data to any HTTP endpoint. ## Setup 1. In Index 01 Settings, tap **Webhook** 2. Pick the gesture to configure: **Hold & talk** or **Double click & hold**. Each gesture has its own endpoint, headers and payload mode 3. Enter your webhook URL 4. Add any request headers you need (e.g. an auth header) 5. Optionally enable **Sign requests** and enter the same signing secret used by your server 6. Choose what to send: Recording only, Transcription only, or Both 7. Optionally tap **Send test event** to verify the endpoint, then **Save** A gesture only sends once its config is saved with a URL. The switch turns sending off without discarding the URL and headers, and the last 20 runs per gesture are listed under **Recent runs**. ## Request Format ``` POST Content-Type: multipart/form-data; boundary= X-Audio-Size: (when audio is included) X-Index-Webhook-Version: 1 X-Index-Trigger: single-click-hold | double-click-hold | test-event X-Index-Test: true (test events only) X-Index-Signature: (when signing is enabled) X-Index-Timestamp: (when signing is enabled) X-Index-Delivery: (when signing is enabled) ``` ## Multipart Fields ### `audio` (conditional) Included when payload mode is **Recording only** or **Both**. - Content-Type: `audio/mp4` - Filename: `.m4a` - Format: AAC-LC encoded in M4A container, mono, 16kHz ### `transcription` (conditional) Included when payload mode is **Transcription only** or **Both**. - Plain text transcription of the recording ### `test` (test events only) Set to `"true"` for payloads sent by **Send test event** , so they cannot be mistaken for a recording. Test events carry no `audio` part and a fixed `transcription`. ### `recordedAt` (always) Unix timestamp in milliseconds when the recording was captured. ### `client` (always) Always set to `"ring"`. ## Payload Modes | Mode | `audio` | `transcription` | `recordedAt` | `client` | | --- | --- | --- | --- | --- | | Recording only | Yes | No | Yes | Yes | | Transcription only | No | Yes | Yes | Yes | | Both | Yes | Yes | Yes | Yes | ## Headers Headers are fully user-configurable in the webhook settings — add as many name/value pairs as you need. They are sent verbatim on every request, so use them for authentication (e.g. an `Authorization` or `X-Widget-Token` header) or any other metadata your server expects. `X-Audio-Size` is still added automatically when audio is included (it carries the audio byte count) and cannot be overridden. `X-Index-Trigger` is added automatically to identify the gesture that started the recording — `single-click-hold`, `double-click-hold`, or `test-event` for a manually sent test. The gesture is persisted with the processing task and preserved when a failed recording is retried. Recordings with no known gesture do not fire a webhook at all. `X-Index-Webhook-Version` is sent on every request and versions the complete webhook protocol, including its automatic headers, multipart fields, and signing rules. This document describes version `1`. A missing version identifies a request from an older, pre-version app build; receivers that depend on a particular contract should require a supported explicit version. User-configured headers cannot override `X-Audio-Size`, `X-Index-Webhook-Version`, `X-Index-Trigger`, `X-Index-Test`, `X-Index-Signature`, `X-Index-Timestamp`, or `X-Index-Delivery`. Header-name matching is case-insensitive. > Migration note: a previously configured single webhook is copied to both recording gestures, and an auth token configured before headers were user-settable is carried over as an `X-Widget-Token` header. ## Protocol versioning Increment `X-Index-Webhook-Version` only for receiver-visible breaking changes, such as renaming or removing automatic headers or multipart fields, changing field semantics or encoding, or changing the signed-byte format. Backward-compatible additions remain on the current version; receivers should ignore headers and multipart fields they do not use. Record every version change in this document, including migration guidance when applicable. - **Version 1:** Current multipart request contract and optional HMAC-SHA256 signing protocol documented below. ## Signed requests Request signing is optional and configured independently for each recording gesture. The secret is stored in Android Keystore-backed encrypted storage or Apple Keychain, rather than in the normal webhook settings JSON. If signing is enabled but the secret cannot be read, the request fails without sending an unsigned fallback. The app signs these exact bytes: ``` UTF8( "v1\n" + timestamp + "\n" + deliveryId + "\n" + trigger + "\n" + (isTest ? "1" : "0") + "\n" ) || rawMultipartBodyBytes ``` The signing key is the UTF-8 byte sequence of the secret exactly as entered. The signature header is: ``` X-Index-Signature: ``` The leading `v1` in the signed bytes authenticates `X-Index-Webhook-Version: 1`; a receiver must use the header value to reconstruct that prefix. For a recording, `X-Index-Delivery` is the stable recording delivery identifier. A test event gets a fresh UUID. `X-Index-Timestamp` is generated when the request is sent. Receivers should: 1. Require HTTPS. 2. Read the raw request body before parsing multipart fields. 3. Reject unsupported webhook versions and malformed timestamps. 4. Reconstruct the signed bytes and compare signatures in constant time. 5. Reject timestamps outside a short window; five minutes is a reasonable default. 6. Cache accepted delivery IDs for at least that window and reject replays. Use a unique, cryptographically random secret for each endpoint. At least 32 random bytes encoded as base64url or hex is recommended. Do not use a memorable password or reuse an API token. HMAC authenticates the request and detects modification; it does not encrypt the recording or transcription. HTTPS is still required. ## Other authentication Custom headers can still carry another authentication scheme alongside request signing. The original integration used an `X-Widget-Token` header, but any scheme works. ## Example: Verifying a signed request ``` import hashlibimport hmacimport osimport timefrom flask importFlask,requestapp=Flask(\_\_name\_\_)secret=os.environ["INDEX\_WEBHOOK\_SECRET"].encode("utf-8")seen\_deliveries={}@app.route('/webhook',methods=['POST'])def receive():raw\_body=request.get\_data(cache=True)webhook\_version=request.headers.get('X-Index-Webhook-Version','')signature=request.headers.get('X-Index-Signature','')timestamp\_text=request.headers.get('X-Index-Timestamp','')delivery\_id=request.headers.get('X-Index-Delivery','')trigger=request.headers.get('X-Index-Trigger','')is\_test=request.headers.get('X-Index-Test')=='true'ifwebhook\_version!='1':return'Unsupported webhook version',400try:timestamp=int(timestamp\_text)exceptValueError:return'Invalid timestamp',400now=int(time.time())ifabs(now-timestamp)\>300:return'Expired request',401prefix=f"v{webhook\_version}\n{timestamp}\n{delivery\_id}\n{trigger}\n{1 if is\_test else 0}\n"expected=hmac.new(secret,prefix.encode('utf-8')+raw\_body,hashlib.sha256,).hexdigest()ifnothmac.compare\_digest(expected,signature):return'Invalid signature',401ifdelivery\_idinseen\_deliveries:return'Duplicate delivery',409seen\_deliveries[delivery\_id]=nowaudio=request.files.get('audio')transcription=request.form.get('transcription')recorded\_at=request.form.get('recordedAt')trigger=request.headers.get('X-Index-Trigger')ifaudio:audio.save(f'/tmp/{audio.filename}')print(f'Received audio: {audio.filename}')iftranscription:print(f'Transcription: {transcription}')print(f'Recorded at: {recorded\_at}')print(f'Trigger: {trigger}')return'OK',200 ``` The in-memory replay cache above is only illustrative. A production service should expire entries and use a shared TTL store when more than one server instance accepts webhooks. ## Notes - Uploads are async and non-blocking — they don't delay the normal recording pipeline - Failed uploads are retried on the next recording (no persistent retry queue) - The webhook fires as early as its payload allows, in parallel with the rest of the pipeline: `RecordingOnly` sends as soon as the audio is on disk (before transcription); modes that include the transcript send once it is transcribed, concurrently with agent processing. The webhook therefore fires even if agent processing (or, for the recording-only mode, transcription) later fails. - Audio is the same 16kHz resampled version used for transcription --- # Community Source: https://developer.repebble.com/community/ # Community ### [Developer Forum](https://forum.repebble.com/c/developers-ask-questions-and-get-help/7) Have questions or need help? The [developer forum](https://forum.repebble.com/c/developers-ask-questions-and-get-help/7) is the best place to ask questions and get help from other Pebble developers. ### [Online Communities](https://developer.repebble.com/community/online.md) Connect with other Pebble developers on the forum. ### [Example Apps](https://developer.repebble.com/community/apps.md) Open source example apps from the community. Use them to learn advanced techniques or as inspiration for your own apps. ### [Developer Tools](https://developer.repebble.com/community/tools.md) Community submitted developer tools. ### [Libraries](https://developer.repebble.com/community/libraries.md) Open source libraries to help supercharge your app development. --- # Submission Guidelines Source: https://developer.repebble.com/community/submission/ We are putting together a collection of community resources for a new section on the Pebble Developer Website, and we need _your_ contributions! There are three types of content we are looking for: - **[Example Apps](#example-apps)** - **[Developer Tools](#developer-tools)** - **[Libraries](#libraries)** To add your resource to the collection, simply [fork this GitHub repository](https://github.com/pebble/community-resources/fork), follow the relevant guide below to create a Markdown file in the right folder, and submit a pull request. You can also take a look at some of the resources that have already been submitted to see how to do it. ### Example Apps We are looking for well-written apps that other people can use to learn Pebble development. > **NOTE:** You must provide an open-source license for your apps in order for it to be approved. The best way to do that is to include a LICENSE file in your app repository. Indicate the license you have selected in the metadata block of your Markdown file. To add your app, create a new Markdown file in the correct folder with the same name as your app (in a URL friendly style, such as `multi-timer.md` or `uk-transport.md`). Examples: ``` apps/multi-timer.md apps/uk-transport.md ``` At the top of your Markdown file you will need to have a metadata block that contains the following properties: ``` ---name: Multi Timercreator: Matthew Tolelicense: mitlink: https://github.com/smallstoneapps/multi-timer/appstore: 52bda40bdc2f4f552e000089tags:- watchapp- js- storage--- ``` Hopefully `name`, `creator` and `link` are self-explanatory. The `appstore` entry is the Pebble appstore ID for your app. If your app isn't on the Pebble appstore, omit this line. The `tags` list will be used on the site to let people browse by different tags. See below for the list of all of the possible tags. Below this metadata block you should write a description of your app. Include details of the key features it has. #### Tags These are all the possible tags for your app. If you think a tag should be added to the list, get in touch with us [using the contact form](https://developer.repebble.com/contact.md). | Tag Name | Description | | --- | --- | | `watchapp` | The app is a watchapp | | `watchface` | The app is a watchface | | `android` | The app has an Android companion app | | `ios` | The App has an iOS companion app | | `js` | The app uses PebbleKitJS | | `pebblejs` | The app is written with the Pebble.JS library | | `storage` | The app uses Persistent Storage | | `config` | The app has a config page | ### Developer Tools To add your tool, create a new Markdown file in the correct folder with the same name as your tool, in a URL-friendly style, such as `pebble-local-simulator.md` or `watchface-generator.md`. Examples: ``` tools/pebble-local-simulator.mdtools/watchface-generator.md ``` At the top of your Markdown file, you will need to have a metadata block that contains the following properties. ``` ---name: Watchface Generatorcreator: Paul Rodelink: http://watchface-generator.de--- ``` Below this metadata block you should write a description of your tool. ### Libraries > **NOTE:** You must provide an open-source license for your library in order for it to be approved. The best way to do that is to include a LICENSE file in the repository for your library. Indicate the license you have selected in the metadata block of your Markdown file. To add your library create a new Markdown file in the `libraries` folder with the same name as your library, in a URL-friendly style, such as `js-message-queue.md` or `clock-layer.md`. Examples: ``` libraries/clock-layer.md libraries/js-message-queue.md ``` At the top of your Markdown file you will need to have a metadata block that contains the following properties. ``` ---name: JS Message Queuecreator: Matthew Tolelicense: mitlink: https://github.com/smallstoneapps/js-message-queue/language: js--- ``` The `language` property should be one of `c`, `js`, `android`, or `ios`. This will help people find your library. Below the metadata block you should write a description of your library. Talk about what problem it aims to solve and how people should use it. --- # Developer Retreat 2014 Source: https://developer.repebble.com/community/events/developer-retreat-2014/ ## Overview Here is the recap of Pebble’s second annual Developer Retreat! Team Pebble and 100 of the best Pebble developers from around the world spent four days learning more about new APIs, hacked on apps, ate our way around Silicon Valley, battled robots, and had a TON of fun! We've put all of the material from the course of the four days here and will be updating it as we finish editing photos, videos, and presentations! It was terribly sad when it was all over and we had to go home. If you missed out this year sharpen up your apps for next year and apply! You can check out what the full schedule was like [here.](https://developer.repebble.com/community/events/developer-retreat-2014/detailed-schedule.md) ## Day 1 ### Opening Presentations #### Eric Migicovsky Eric Migicovsky, CEO and Founder of Pebble, kicks off the 2014 Annual Pebble Developer Retreat with some words about the last year of Pebble and what is yet to come. #### Thomas Sarlandie Thomas Sarlandie, Head Developer Evangelist at Pebble, welcomes everyone to the second ever Developer Retreat and goes through a detailed agenda of the next 4 days including some interesting facts about Pebble development over the past year. Also featured: New stuff in 2.6 - Background Workers Coming up in 2.7 - Wakeup API [Presentation on SlideShare](http://www.slideshare.net/pebbledev/pebble-dev-retreat2014kickoff) ### Advanced Techniques Heiko Behrens and Matthew Hungerford talk about advanced programming techniques for Pebble. This was a series of three presentations wrapped up into one but is broken out into three videos for your viewing pleasure. #### Size This talk focused on size to optimize pebble apps for code size, heap space, and advice on use of floating point. The Mandelbrot demo and XKCD app were featured. [Presentation on SlideShare](http://www.slideshare.net/pebbledev/pebble-dev-retreat2014size) #### Graphics This talk focused on graphics techniques including run-time dithering, offline dithering, pixel manipulations, and frame-buffer drawing. This talk featured the amiga boing ball dithering demo. [Presentation on SlideShare](http://www.slideshare.net/pebbledev/pebble-dev-retreat2014graphics) #### 1010000 101 on Pebble Development but 10000 times cooler (AKA 0X50 or "P"). This was a shared experience of developing for Pebble on Mac and Linux using various tools and libraries. This videos features frame-buffer app demo, experimenting with graphics using python, AppCode, and PebbleGL (openGL). ## Day 2 ### Pebble Presentations #### Battery Life Stuart Harrell, software engineer at Pebble, gives a talk about battery life and how to preserve it. This talk explains the effect of different system functions on battery life and discusses solutions to common battery life problems with apps. [Presentation on SlideShare](http://www.slideshare.net/pebbledev/pebble-devretreat2014-battery) #### BLE Central APIs Martijn Thé, software engineer at Pebble, talked about an experimental SDK with BLE APIs that developers were able to use at the retreat. This information will be shared once the APIs are public. ### Guest Presentations #### Strap This talk given by Steve Caldwell, CEO/CTO of Strap, focused on how to add analytics to your Pebble app. [Presentation on SlideShare](http://www.slideshare.net/pebbledev/guest-presentation-strap-pebble-developer-retreat-2014) #### Overlay Ron Ravid and Grégoire Sage cover the Overlay technique and how to load parts of code from resources. [Presentation on SlideShare](http://www.slideshare.net/pebbledev/pebble20141003-overlay03) ### Computer History Museum Photo Booth [Photo Booth Photos on Google+](https://plus.google.com/photos/118265358803224468726/albums/6070990535833345361) ## Day 3 ### Robotics Competition #### Videos Interested in almost 4 hours of action packed robot on robot action? Check out the live stream footage we aired during the competition! There were BLE robots using [LightBlue Bean](https://punchthrough.com/bean/) boards and wifi robots using [Spark](https://www.spark.io/) boards! * * * #### Photos [Photo Booth Photos on Google+](https://plus.google.com/photos/118265358803224468726/albums/6070995613414095969) ##### Bracket Results We kept track of the competition bracket on a super official whiteboard. ![](https://developer.repebble.com/assets/images/community/events/developer-retreat-2014/robotcompetition.png) * * * ## Day 4 ### Awards ###### Most \<3 watchapp in appstore Robert Hesse for [Smartwatch+](https://apps.getpebble.com/applications/52f0573d1ac7949119001518) ###### Most \<3 watchface in appstore David Rodríguez Ríncon for [YWeather](https://apps.getpebble.com/applications/52cc44e045ffdd31dd000180) ###### Rocks the Pebble Forums Jon Barlow ([orviwan](http://forums.getpebble.com/profile/7316/orviwan)) ###### Robot Masterminds Winning Team: The Keggers (KEG, FlutterBot, Footbot) ![](https://developer.repebble.com/assets/images/community/events/developer-retreat-2014/robotwinners.png) ###### Best Developer Retreat Hack Eric Oesterle for PEBBLE PEBBLE DISCO for PEBBLE ![](https://developer.repebble.com/assets/images/community/events/developer-retreat-2014/erichat.png) * * * ###### Thanks from all of us here at Pebble's Developer Experience team and we hope to see you next year! ![](https://developer.repebble.com/assets/images/community/events/developer-retreat-2014/devexpteam.png) --- # Developer Retreat 2014 Schedule Source: https://developer.repebble.com/community/events/developer-retreat-2014/detailed-schedule/ ### Thursday October 2nd #### 09:30 - Start of the Retreat - Check-in at front desk to pick up swag, Pebble, and sign License Agreement. - Get breakfast and mingle with your fellow attendees. #### 10:30 - Opening Presentation Eric Migicovsky & Thomas Sarlandie will do a introduction presentation, featuring: - Welcome to the Developer Retreat - Detailed agenda - New stuff that came out in 2.6 - Coming up in 2.7 - How to give feedback - How to recognize Pebble staff - Questions? #### 11:30 - Mingling You will have the chance to go and start playing with the new stuff but will also have the opportunity to mingle with the Pebble staff. #### 12:00 - Lunch Grab lunch from the auditorium and eat in the main room. #### 13:00 - Technical Presentations Heiko Behrens and Matthew Hungerford will discuss advanced APIs and developer techniques. - Size - Graphics - Productivity #### 16:00 - Leave via Caltrain for Meetup in SF - 20 min walk to Caltrain in Mountain View (or use Uber/Lyft) - 16:35 - Caltrain from Mountain View to SF (Bullet arrives at 17:24) - Next one is 16:46 arriving at 17:39 or 17:03 ariving at 18:02 - There is a 20 min walk from the Caltrain to the venue of the Meetup (or use Uber/Lyft) > Geekdom by Rackspace > > [http://geekdomsf.com/](http://geekdomsf.com/) > > [620 Folsom (by 2nd st.) · 1st Floor · San Francisco, CA 94107](https://www.google.com/maps/place/620+Folsom+St,+San+Francisco,+CA+94107/@37.785271,-122.3975806,17z/data=!3m1!4b1!4m2!3m1!1s0x8085807c44912c69:0xa95771345c6d698e) #### 18:00 -\> 18:40 - Meeting and Mingling Mingle with the Pebble developers and enthusiasts attending the Meetup in SF. #### 18:40 -\> 19:00 - Introduction - Rackspace/Geekdom Introduction - Intro from Pebble #### 19:00 -\> 19:30 - Technical Talks - Jeremiah Robison from Jawbone will present the Jawbone UP app - Ron Ravid will cover his Overlay system to load code from resources with Q&A - Marcel Jackwerth will cover Pebble Preview / Pebble Mirror: A way to quickly design on a computer and see the results on a Pebble with Q&A #### 19:30 -\> 20:15 - Lightning Talks Various 5 min talks from retreat attendees and Meetup community on what they've worked on with Pebble #### 20:30 - Raffle 3 Pebble Steels #### 21:30 - Meetup Ends Attendees take Caltrain back to Mountain View: 20:40, 21:40, 22:40 - 1 hour and 8 minutes back. ### Friday - October 3rd #### 09:30 - Start of Day 2 - Check-in desk will still be open for latecomers. - We will check badge at entrance - Breakfast will be served in the auditorium - Developers are invited to go to the main room to mingle and work on their apps. #### 10:30 -\> 11:00 - Battery Talk by Stuart Harrell Stuart will give a 30 minute talk about battery life in the auditorium. After the talk, Stuart will move to the Workshop Room and will be available there to continue the discussion with developers interested in getting more info. #### 11:00 -\> 11:30 - BLE Presentation by Martijn Thé - We will distribute an experimental SDK with BLE APIs to the developers interested. - We will then have a selection of BLE accessories available in the Workshop Room so that you play with them and build apps that talk to them. #### 12:00 - Lunch #### 14:00 - Code Reviews in the Workshop Room Throughout the afternoon you can go and talk to a Pebble engineer in the workshop room to get your code reviewed. #### 14:30 -\> 15:00 - Workshop on CloudPebble Overview of new features in CloudPebble and a refresher on the basics #### 15:00 -\> 15:30 - Guest Presentation by Strap A presentation about adding analytics to your Pebble app. #### 15:30 -\> 16:00 - Guest Presentation on Overlay Ron and Gregoire will cover the Overlay system in more detail from their shorter presentation at the Meetup. #### 15:30 -\> 17:30 - Pantelligent demo w/ Pebble and Crepes Crepe making in the main hacking room kitchen with the use of the Pantelligent. #### 16:00 -\> 16:30 - Workshop on Pebble TM Christy La Pierre will hold a workshop to get feedback on apps that use "Pebble" in their name. #### 17:30 - Travel to Computer History Museum A bus will take two groups of people to the Computer History Museum (17:30 and 18:00). There is a large parking lot at the museum if you choose to drive or bike. > Computer History Museum > > [http://www.computerhistory.org](http://www.computerhistory.org) > > [1401 N Shoreline Blvd, Mountain View, CA 94043](https://www.google.co.uk/maps/place/Computer+History+Museum/@37.414274,-122.077409,17z/data=!3m2!4b1!5s0x808fb75692370241:0x72ed99a28de17bdb!4m2!3m1!1s0x808fb7569249b39b:0xea8071641d7ef4f2) #### 18:00 - Self-Guided Tours of the Computer History Museum The venue will be closed for our private event and a photo booth will be set up in front of The Babbage Engine! #### 19:00 - Dinner at the Computer History Museum #### 22:00 - Leave the Computer History Museum A bus will take two groups of people back to the event venue (19:30 and 22:00). ### Saturday - October 4th #### 09:30 - Start of Day 3 - Check-in will still be open for latecomers - We will check badge at entrance - Breakfast will be served in the auditorium - You're invited to go to the main room and hack #### 10:00 -\> 12:00 - Code Reviews in the Workshop Room Throughout the morning you can go and talk to a Pebble engineer in the Workshop Room to get your code reviewed and ask questions. #### 10:30 - Robot Competition Introduction - Rules and demo of the robot will be given - People will form teams of 2 or 3 - Move to the Workshop Room for soldering station #### 12:00 - Lunch #### 14:00 -\> 17:00 - Code Reviews in the Workshop Room Throughout the afternoon you can go and talk to a Pebble engineer in the Workshop Room to get your code reviewed and ask questions. #### 16:00 - Robot Competition Matches #### 18:00 - Dinner #### 20:00 - Ice Cream Sandwich Bar by Cream Make your very own ice cream sandwich out of lemon, chocolate chip, or oatmeal raisin cookies with blueberry soy cream, French vanilla, or rocky road ice cream in the center. Limited vegan snickerdoodle and blueberry soy cream pre-made sandwiches are available. #### 22:00 - End of Day 3 ### Sunday October 5th #### 09:30 - Start of Day 4 Welcome developers and have pastries for breakfast. #### 11:30 - Brunch #### 12:30 - Developer Showcase Take 10 minutes to show us what you've built on Pebble over the weekend or in the past that you're proud of! #### 14:00 - Awards Ceremony The awards are still a secret! #### 14:30 - Final Words #### 15:30 - Official End of Retreat Thank you all for coming and we hope you had a great time with us! --- # Developer Retreat 2015 Source: https://developer.repebble.com/community/events/developer-retreat-2015/ ## Overview Now that the dust has settled from Pebble’s third annual Developer Retreat, it's time to recap! Team Pebble and 50 of the best Pebble developers from around the world spent four days learning about designing for round displays, talking to our wrists, getting the first glance at Pebble Time Round, eating our fill of delicious foods, and finally touring around San Francisco to see the sights! It was terribly sad when it was all over and we had to go home. But there's always next year! If you missed out this year [let us know you're interested in next year's retreat](javascript:void();)! You can check out what the full schedule was like [here](https://developer.repebble.com/community/events/developer-retreat-2015/detailed-schedule.md). We've now finished uploading all of the materials from the retreat so you can watch along with the attendees. You can find the full content of [photos](https://www.flickr.com/photos/137169991@N07/albums), [videos](https://www.youtube.com/playlist?list=PLDPHNsf1sb48bgS5oNr8hgFz0pL92XqtO), and [presentations](http://www.slideshare.net/pebbledev/clipboards/pdr15) on their respective websites! ## Day 1 ### Pictures (Day 1) Full album on Flickr from Day 1 [can be found here](https://www.flickr.com/photos/137169991@N07/albums/72157659708415990) or you can see some in the slideshow below. ### Main Stage Presentations #### Opening Ceremony Eric Migicovsky (CEO) and Thomas Sarlandie (Director of Apps and Developer Ecosystem) officially welcome everyone to the 2015 Pebble Developer Retreat. #### Designing Apps for Pebble Liron Damir (Head of Design) discusses compelling design for Pebble and touches on the challenges of designing apps on a round screen. Full slidedeck: [**#PDR15 - Designing Apps for Pebble**](//www.slideshare.net/pebbledev/pdr15-designing-for-pebble) #### Developing for Round A talk by Heiko Behrens (Developer Experience Engineer) about Pebble Time Round and designing for Chalk. Full slidedeck: [**#PDR15 - Developing for Round**](//www.slideshare.net/pebbledev/03-pdr15-developing-for-round) ### PebbleSF Meetup We had our 1st anniversary of the PebbleSF Meetup during the retreat! Our attendees met the rest of the local community here in the Bay Area and socialized with more members of Team Pebble. We enjoyed delicious tacos, drinks, community demos, and great people! Here is a list of the presentations during the Meetup which can all be seen during the video: - Heiko Behrens (Pebble Developer Experience Engineer) discusses developing for Pebble Time Round with the Meetup community. - Nate Stockham (Project Kraepelin) talks about Wearable Psychiatry at Stanford, using Pebble to diagnose and continuously monitor the most devastating psychiatric disorders. - Anthony Paglino (Seeed Studio) talks about Pebble + Xadow Module and what this means for smartstraps. - Mathew Reiss (Retreat Attendee) talks about Simple Dithering Library, a simple concept that drastically opens up the range of perceivable colors on the Pebble Time. ## Day 2 ### Pictures (Day 2) Full album on Flickr from Day 2 [can be found here](https://www.flickr.com/photos/137169991@N07/albums/72157659712403598) or you can see some in the slideshow below. ### Side Sessions (Day 2) #### Creating Pebble Apps for Aplite, Basalt, and Chalk Curious about how to design apps that look great on Pebble Classic, Pebble Time, and Pebble Time Round? Confused about how to structure and implement code for multi-platform apps using the Pebble SDK? Kevin Conley (Embedded Developer) will cover these topics as well as share several tips, tricks, and tools for creating amazing apps that run on all Pebble devices. Full slidedeck: [**#PDR15 - Creating Pebble Apps for Aplite, Basalt, and Chalk**](//www.slideshare.net/pebbledev/pdr15-creating-pebble-apps-for-aplite-basalt-and-chalk) #### Pebble Graphics Matthew Hungerford (Developer Experience Engineer) pushes pixels to their limits as he talks about graphical effects leveraging Pebble APIs and community libraries to create exceptional watchfaces and apps. Full slidedeck: [**#PDR15 - Pebble Graphics**](//www.slideshare.net/pebbledev/pdr15-pebble-graphics) #### Voice on Pebble Andrew Stapleton (Embedded Developer) deep dives into the internals of the Pebble Dictation API. Full slidedeck: [**#PDR15 - Voice on Pebble**](//www.slideshare.net/pebbledev/pdr15-voice-api) #### PebbleKit iOS 3.0 Find out what has changed since PebbleKit iOS 2.0 and learn how to upgrade your projects. Discover how to take advantage of features of the new PebbleKit iOS with Marcel Jackwerth (iOS Developer). Full slidedeck: [**#PDR15 - PebbleKit iOS 3.0**](//www.slideshare.net/pebbledev/pdr15-pebblekit-ios-30) #### waf, wscript and Your Pebble App Learn how to take advantage of the Pebble build system by creating customized wscripts that let you concatenate JS files, automatically run linters, and internationalize your apps with Cherie Williams (Developer Evangelist). Full slidedeck: [**#PDR15 - waf, wscript and Your Pebble App**](//www.slideshare.net/pebbledev/pdr15-waf-wscript-and-your-pebble-app) #### Best Use Cases for Timeline Learn all about the Pebble timeline. Specifically when to use it and when not to use it. See some of the best use cases for timeline and find out why timeline gives your app a competitive advantage with Jon Barlow (Developer Advocate). Full slidedeck: [**#PDR15 - Best Use Cases for Timeline**](//www.slideshare.net/pebbledev/pdr15-best-use-cases-for-timeline) #### Building a Great Appstore Page A short presentation to learn about creating awesome assets for the Pebble appstore with Aaron Cannon (Product Management). Full slidedeck: [**#PDR15 - Awesome Appstore Assets**](//www.slideshare.net/pebbledev/pdr15-awesome-appstore-assets) #### Smartstraps Workshop This session covered everything needed to get started with smartstraps, from the hardware connection to APIs on the watch to the Arduino smartstrap library with Brian Gomberg (Embedded Developer). This is the same talk we gave at Pebble Rocks Boulder and we have a similar video from the September PebbleSF Meetup if you're interested in smartstraps. Full slidedeck: [**#PDR15 - Smartstrap Workshop**](//www.slideshare.net/pebbledev/pdr15-smartstrap-workshop) ## Day 3 ### Pictures (Day 3) Full album on Flickr from Day 3 [can be found here](https://www.flickr.com/photos/137169991@N07/albums/72157660051112379) or you can see some in the slideshow below. ### Side Sessions (Day 3) #### PebbleAMS Live! We did our first live Google Hangout with the developers at a [Pebble Meetup in Amsterdam](http://www.meetup.com/PebbleAMS/events/225180284/)! Jaron and Kristof of [Kiezel Watchfaces](https://www.kiezelwatchfaces.com/) gave us a hint of [KiezelPay](https://kiezelpay.com/). You can sign up to learn more about in-app payments without creating your own mobile clients on their website. #### Pebble Meetups Group Discussion A group discussion hosted by Kat McAuliffe (Event Engineer) about the worldwide Pebble Meetups. The feedback from this session helped spawn the new [Meetup Website](https://developer.repebble.com/community/events/meetups/)! If you're interested in recreating a piece of the developer retreat where you live, please contact us using that page's contact form. #### The Good, the Bad and the Ugly A group discussion hosted by Katharine Berry (Developer Tools Manager) about Pebble APIs and Tools. This session filled the boardroom to capacity with retreat attendees who eagerly wanted to put forward their ideas and experiences of developing for Pebble. This session was a great success and this valuable feedback is being reviewed internally by the relevant teams and the requests will hopefully be integrated into future development plans. #### A Community of Pebblers! A group discussion hosted by Jon Barlow (Developer Advocate) about the Pebble community. Forums, Slack, surveys, code samples, our newsletter, and community projects are just a few of the things we covered during this session. We got a lot of feedback about devsupport that we've already put in practice and response times will keep getting better! We love how much Slack has helped us connect with our community and to each other! ### Presentations #### Data Analytics and Pebble Jack Chai (Data Scientist) takes us through the new Developer Dashboard analytics and delves into battery performance. He also reveals some interesting insights into levels of user interaction and how your projects could benefit from integration with timeline. Full slidedeck: [**#PDR15 - Data Analytics and Pebble**](//www.slideshare.net/pebbledev/pdr15-data-analytics-and-pebble) #### Demos and Closing Ceremonies It was a whirlwind of code, design, friendships, and collaboration after 3 days at the retreat. We gave everyone the chance to demo what they had been working on (see mark 5:03 for demos) to the audience and then we recapped the event. ### Awards Awards were given out after closing ceremonies to six individuals who were chosen either by popular vote or Team Pebble for their extraordinary achievements. A list of the awards and their awardees are below. **Retreat Attendee Voted Awards** - Most Creative Project - Mathew Reiss - Most Beautiful Design - John Reno La Motta - Most Clever Use of Circles - Caitlin McDonald **Team Pebble Voted Awards** - Best Technical Achievement - Max Bäumle - Best Open Source Contribution - Alessio Bogon - Lifetime Achievement Award - Paul Rode ## Day 4 ### SF Sightseeing Trips The last day was reserved for having fun! There were three sightseeing tour options around San Francisco that attendees and Pebblers got to choose from: Segway Tour of Golden Gate Park, Segway Tour of Fisherman’s Wharf, and Motorized Cable Car City Tour. Photos below capture attendee's collective photos of the day, let us know if you have ones to share that aren't in the [full album](https://www.flickr.com/photos/137169991@N07/albums/72157660166881775)! **Thanks from all of us here at Team Pebble for another wildly successful retreat, we hope to see you next year!** Enter your email address below to get on the list to find out more about the 2016 Pebble Developer Retreat. --- # Developer Retreat 2015 Detailed Schedule Source: https://developer.repebble.com/community/events/developer-retreat-2015/detailed-schedule/ This is an updated schedule as of October 5th. We've also created a [useful map](https://www.google.com/maps/d/edit?mid=zvBoeJSSmPsE.kw0K-QoML8aA&usp=sharing) of the key event locations. All sessions and presentations will be formally recorded and released after the retreat. ### Wednesday, October 7th #### 18:00 - Warmup Party! Drinks and appetizers to meet your fellow attendees and Team Pebble @ The Starlight Room - 450 Powell St, San Francisco. ### Thursday, October 8th #### 09:00 - Retreat Begins Doors open at [SF Greenspace](http://www.sfgreenspace.com/pages/location). Get registered and grab your swag! We'll have place tags at seats for the first day. So find your seat and get settled! #### 09:00 - Breakfast Grab some grub! #### 10:00 - Official Kickoff Eric Migicovsky (CEO) and Thomas Sarlandie (Director of Apps and Developer Ecosystem) officially welcome everyone to the 2015 Pebble Developer Retreat. #### 10:30 - Designing for Pebble Liron Damir (Head of Design) discusses compelling design for Pebble. #### 11:00 - Pebble Time Round & Chalk A talk by Heiko Behrens (Developer Experience Engineer) about Pebble Time Round and designing for Chalk. #### 12:00 - Lunch Delicious nom noms. #### 13:00 - Watchface-a-thon Get porting your watchfaces to Chalk, or begin designing some for the first time! #### 18:00 - PebbleSF - Meetup - Departure Head on over to the [PebbleSF Meetup](http://www.meetup.com/PebbleSF/events/225619118/) @ [Geekdom](http://geekdomsf.com/) - 620 Folsom St, San Francisco. You do not need to RSVP on Meetup.com if you don't want to. We've considered all attendees as attending. #### 18:30 - PebbleSF - Meetup Begins This will be a Meetup similar to [what we did last year](http://www.meetup.com/PebbleSF/events/208002662/) to meet the rest of the local community here in the Bay Area and socialize with more members of Team Pebble. Enjoy delicious tacos, drinks, community demos, and great people! #### 19:00 - PebbleSF - Presentations Heiko Behrens (Developer Experience Engineer) discusses developing for Pebble Time Round with the Meetup community. Nate Stockham (Project Kraepelin) talks about Wearable Psychiatry at Stanford, using Pebble to diagnose and continuously monitor the most devastating psychiatric disorders. Anthony Paglino (Seeed Studio) talks about Pebble + Xadow Module and what this means for smartstraps. Mathew Reiss talks about Simple Dithering Library, a simple concept that drastically opens up the range of perceivable colors on the Pebble Time. #### 20:15 - PebbleSF - Watchface Demos Retreat attendees can unveil their new watchfaces to the community or anything else they have been working on lately. Please [sign up here to demo](https://docs.google.com/spreadsheets/d/1BBpWGgWOJ5MMCmfIEgyS4nVKtHYNBnKJoQVttKyYv-M/edit#gid=937215591). #### 21:30 - PebbleSF - Meetup Ends You can continue the celebration if you'd like or go back to the hotel! ### Friday, October 9th #### 09:00 - Day 2 Begins Doors open, make yourselves at home. You can sit anywhere you like from here on out. #### 09:00 - Breakfast Grab some grub! #### 09:30 - Creating Pebble Apps for Aplite, Basalt, and Chalk Curious about how to design apps that look great on Pebble Classic, Pebble Time, and Pebble Time Round? Confused about how to structure and implement code for multi-platform apps using the Pebble SDK? Kevin Conley (Embedded Developer) will cover these topics as well as share several tips, tricks, and tools for creating amazing apps that run on all Pebble devices. #### 10:30 - Pebble Graphics Lets push those pixels to their limits as Matthew Hungerford (Developer Experience Engineer) talks about graphics effects leveraging Pebble APIs and community libraries to create exceptional watchfaces and apps. #### 11:30 - Voice API Andrew Stapleton (Embedded Developer) deep dives into the internals of the Pebble Dictation API. #### 12:00 - Lunch More delicious noms! #### 13:00 - PebbleKit iOS 3.0 Find out what has changed since PebbleKit iOS 2.0 and learn how to upgrade your projects. Discover how to take advantage of the new features of the new PebbleKit iOS with Marcel Jackwerth (iOS Developer). #### 14:00 - waf, wscript and Your Pebble App Learn how to take advantage of the Pebble build system by creating customized wscripts that let you concatenate JS files, automatically run linters, and internationalize your apps with Cherie Williams (Developer Evangelist). #### 15:00 - Best Use Cases for Timeline Learn all about timeline on Pebble. Specifically when to use it and when not to use it. See some of the best use cases for timeline and find out why timeline gives your app a competitive advantage with Jon Barlow (Developer Advocate). #### 16:00 - Awesome Appstore Assets Learn all about creating awesome assets for the Pebble Appstore with Aaron Cannon (Business Development). This will be a lot of discussion about what has worked/not worked for people in the past, so give us your input! #### 18:00 - Dinner! A large tasty meal to keep you happy during your work. #### 19:00 - Smartstraps Workshop This session will cover everything needed to get started with smartstraps, from the hardware connection to APIs on the watch to the Arduino smartstrap library with Brian Gomberg (Embedded Developer). We will have limited hardware for people build with and take home! #### 22:30 - Sugar Rush! It's a secret, but you can probably guess what it is! #### 23:59 - SF Greenspace Closes Go back to the hotel and get some sleep, you've earned it! ### Saturday, October 10th #### 09:00 - Day 3 Begins Doors open, resume hacking. #### 09:00 - Breakfast Grab some grub! #### 09:30 - PebbleAMS Live! Live Google Hangout with developers at a Pebble Meetup in Amsterdam! #### 10:30 - Fireside Chat with Eric A unique opportunity to chat with Eric and ask him whatever you'd like! #### 11:30 - Pebble Meetups Group Discussion A group discussion with Kat about the worldwide Pebble Meetups. #### 12:00 - Lunch Even more delicious noms! #### 13:30 - The Good, the Bad and the Ugly A group discussion about Pebble APIs and Tools. #### 14:30 - A Community of Pebblers! A group discussion about the Pebble community. Forums, feedback, surveys, code samples, community projects and other ways to help the Pebble ecosystem. #### 17:30 - Data Analytics and Pebble Jack Chai (Data Scientist) takes us through the new Developer Dashboard analytics and delves into battery performance. He also reveals some interesting insights into levels of user interaction and how your projects could benefit from integration with timeline. #### 18:00 - Dinner More food. Enjoy! #### 19:00 - Closing Ceremonies Begin Time to wind things down. #### 20:00 - Developer Demos Show demos of what you've created during your time at the retreat. #### 20:30 - Prizes Awards for attendees for what you've demoed and closing remarks. #### 21:00 - SF Greenspace Closes You don't have to go home, but you can't stay here! ### Sunday, October 11th #### 9:00 to 9:45 - Day 4 Begins Meet your fellow tourists and share Uber rides to your specific tour starting point. Please see the calendar invite send to you for your specific tour with meet times. #### 10:00 - SF Sightseeing Trips Sightseeing around San Francisco! You were placed into one of three groups based on your interests and should have a calendar invite with further details for your specific trip. Segway Tour of Golden Gate Park - @ 55 Music Concourse Dr, San Francisco, CA 94118 Segway Tour of Fisherman’s Wharf - @ 757 Beach St, San Francisco, CA 94109 Motorized Cable Car City Tour - Leaving from the Pickwick Hotel and dropping off in Golden Gate Park at picnic location. #### 13:00 - Golden Gate Park Farewell Picnic Lunch and fun activities will be provided for the farewell picnic near Cal Academy of Sciences in Golden Gate Park to send you off on your way home! Attendees can gather together and head back to the hotels via Uber/Lyft. #### 15:00 - Retreat Ends Thanks for coming and keep being awesome! --- # Events Source: https://developer.repebble.com/community/events/ | | August 2014 | | | --- | --- | --- | | M | T | W | T | F | S | S | | --- | --- | --- | --- | --- | --- | --- | [Submit an Event](javascript:void();) [Find a Meetup Near You](http://www.meetup.com/pro/pebble/) --- # Online Pebble Communities Source: https://developer.repebble.com/community/online/ We have loads of online communities where you can interact with other Pebble developers, get help with your apps or just hang out and chat. ### [Developer Forum](https://forum.repebble.com/c/developers-ask-questions-and-get-help/7) The [developer forum](https://forum.repebble.com/c/developers-ask-questions-and-get-help/7) is the best place to ask questions and get help with Pebble development. --- # Badge Generator Source: https://developer.repebble.com/community/tools/badge-generator/ Generate cool badges for your Pebble app that show off how many people **love** your watchapp or watchface. Here are some of the different colours and sizes you can use: ![Small Orange Badge](http://pblweb.com/badge/52d30a1d19412b4d84000025/orange/small/) ![Medium Black Badge](http://pblweb.com/badge/52d30a1d19412b4d84000025/black/medium/) ![Large White Badge](http://pblweb.com/badge/52d30a1d19412b4d84000025/white/large/) --- # Developer Tools Source: https://developer.repebble.com/community/tools/ | Name | Creator | | --- | --- | | [App Message Bridge](https://developer.repebble.com/community/tools/appmsgbridge/) | finebyte | | [Appstore Badge Generator](https://developer.repebble.com/community/tools/badge-generator.md) | Matthew Tole | | [GPath.svg](https://developer.repebble.com/community/tools/gpath-svg.md) | Rajendra Serber | | [pBuild Travis CI Build Tool](https://developer.repebble.com/community/tools/pbuild.md) | Stefano Gerli | | [Sublime Text Plugin](https://developer.repebble.com/community/tools/sublime-text-package.md) | James Brooks | | [Watch Status](https://developer.repebble.com/community/tools/watchstatus/) | Max Bäumle | --- # Gpath Svg Source: https://developer.repebble.com/community/tools/gpath-svg/ Convert standard SVG files to a Pebble GPathInfo array that can be drawn with `gpath_draw_filled` or `gpath_draw_outline`. --- # Pbuild Source: https://developer.repebble.com/community/tools/pbuild/ **pBuild** is a open source Travis CI build tool for Pebble SDK. It helps developers that use the Pebble SDK to test their apps in Travis CI. ## How does pBuild works 1. pBuild installs all of the Pebble SDK dependencies in Travis CI 2. Then it fetches the SDK from Pebble download servers based on the version you chose to download. 3. Next the SDK gets installed in Travis CI 4. Finally your app gets built, if it fails Travis CI detects it and if it builds succesfully Travis detects a pass. --- # Sublime Text Package Source: https://developer.repebble.com/community/tools/sublime-text-package/ Sublime Text plugin with SDK completions and commands. Compatible with SDK Version 2.7 ## Super simple installation Using [Sublime Package Control](http://sublime.wbond.net) you can search for Pebble and install directly from Sublime. ## Commands 1. `build` 2. `clean` 3. `install` 4. `screenshot` 5. `ping` --- # Caltrain Source: https://developer.repebble.com/community/apps/caltrain/ Caltrain is a Pebble app that displays upcoming trains at a station, and where those trains will stop along the remainder of each of their routes. It stores the schedule locally in [`resources`](https://developer.repebble.com/docs/c/Foundation/Resources.md "Resources"), and makes heavy use of [`resource_load_byte_range`](https://developer.repebble.com/docs/c/Foundation/Resources.md#resource_load_byte_range "resource\_load\_byte\_range") to read in the required data without running out of RAM. This is wrapped in a block reading API for performance reasons; without that it takes several seconds to load each window. It also uses [`Persistent Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") to store the state of the UI, so it can be restored to the state in which you left it when you return to the app. Finally, it uses PebbleKit JS to retrieve your location on launch. If it gets a response before you manually choose a station, it will automatically show the station closest to you. The windows were built using [CloudPebble's UI Editor](https://developer.repebble.com/blog/2014/08/08/CloudPebble-Graphical-UI-Editor.md). A python script is included to convert from the Caltrain GTFS data to the compressed format it uses. Screenshots: ![](http://i.imgur.com/iGGxV9q.png) ![](http://i.imgur.com/RNeaSQ1.png) ![](http://i.imgur.com/23kD7ie.png) ![](http://i.imgur.com/mHFaUMy.png) --- # Example Apps Source: https://developer.repebble.com/community/apps/ | Name | Creator | License | Tags | | --- | --- | --- | --- | | [Hello](https://developer.repebble.com/community/apps/Hello.md) | James Mattis | MIT | watchapp, ios, messaging | | [Caltrain](https://developer.repebble.com/community/apps/caltrain.md) | Katharine Berry | mit | watchapp, js, storage | | [HeroBoard](https://developer.repebble.com/community/apps/heroboard.md) | Rodrigo Mesquita | mit | watchapps, graphics, animation | | [Multi Timer](https://developer.repebble.com/community/apps/multi-timer.md) | Matthew Tole | mit | watchapp, js, storage, config | | [Resistor Time](https://developer.repebble.com/community/apps/resistor-time.md) | Ben Combee | mit | watchface, js, config, storage, messaging | --- # Hello Source: https://developer.repebble.com/community/apps/Hello/ _ **Hello** _ demonstrates a fairly complete Pebble watch app appMessage implementation including error handling. It outputs a vareity of statistics, and provides convenient settings switches to control some of the app parameters and settings. The basic appMessage demo apps provided by Pebble demonstrate the key features of the App Message system, but don't illustrate all of the potential errors, warning, and errata that you might encounter on a production app trying to send high rates of data to the Pebble Watch via App Message. The Hello watch app demo attempts to provide a more thorough example. Complete details of the iOS and Watch app implementation can be found in the NOTES.md file. There is probably some silly bug in here somewhere. If you find something, let me know about it. Thanks! --- # Heroboard Source: https://developer.repebble.com/community/apps/heroboard/ Guitar Hero-based keyboard for Pebble This is a Pebble app keyboard based on the game Guitar Hero. It is NOT a final product/watchapp. It is meant to be used with T3/T9 text prediction scripts. When complete, it could be implemented as an exclusive keyboard for Pebble. --- # Multi Timer Source: https://developer.repebble.com/community/apps/multi-timer/ Multi Timer is a Pebble watchapp that allows you to run several timers and stopwatches simultaneously. It uses [`Persistent Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") on the watch to remember your timers, and even fakes the timers running in the background by calculating how much time has elapsed since the app was last run and adding / removing this amount of time from any running timers. Some parts of the C and JS code are tested, and are run automatically using [Travis CI](https://travis-ci.org/). The current build status of the app is: [![Build Status](https://travis-ci.org/smallstoneapps/multi-timer.png?branch=master)](https://travis-ci.org/smallstoneapps/multi-timer/) Here is what the app looks like: ![Multi Timer Screenshot #1](https://raw.githubusercontent.com/smallstoneapps/multi-timer/master/marketing/screenshots/2.7/multi-timer_2-7_01.png) --- # Resistor Time Source: https://developer.repebble.com/community/apps/resistor-time/ This shows the current time as a resistor with the color code matching the time of day in 24-hour time. Configuration is done through a static HTML page hosted on github pages and some PebbleKit JS code that handles communication with the configuration page. This only works on the Basalt color platform, as there's no point trying to show color codes on a black and white watch. --- # Color Select Lib Source: https://developer.repebble.com/community/libraries/color-select-lib/ ## Description This library opens a seperate window for color selections. Useful for converting existing apps to color. ## Usage #### 1. Add color\_sel\_lib.h and color\_sel\_lib.c to your Pebble project. ``` #include "color\_sel\_lib.h" ``` #### 2. Define a handler to use the selected color: ``` void handle\_CS\_close(int color\_argb) {GColor color = (GColor){.argb = color\_argb};// Do something// or use directlytext\_layer\_set\_background\_color(text\_layer\_name, (GColor){.argb = color\_argb});} ``` #### 3. Create the window and show it: ``` CSWindow \* myCSWindow = cswindow\_create(default\_color, false, (CSCloseHandler)handle\_CS\_close);cswindow\_show(myCSWindow, true); ``` | Parameter | Description | | --- | --- | | **default\_color** | The default color that the window will try to match. | | **full\_palette** | true to use all 64 color, false to use a subset of 11 main colors. The user can switch between the two palettes by holding the select button. | | **closeHandler** | The CSCloseHandler to fire when the keyboard closes. | --- # Custom Status Bar For Pebble Source: https://developer.repebble.com/community/libraries/custom-status-bar-for-pebble/ A plug and play custom status bar implementation for pebble apps and watch faces. Easily populate 3 [text layers](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") and/or 5 [bitmap layers](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer"). Use: Choose a status bar height and icon size. Icons should be square with a maximum of 28 pixels. ``` #define BAR\_HEIGHT 20#define ICON\_WIDTH\_HEIGHT 15 ``` Declare ``` static CustomStatusBarLayer \*custom\_status\_bar; ``` Create ``` custom\_status\_bar = custom\_status\_bar\_layer\_create(BAR\_HEIGHT, GColorBlack, ICON\_WIDTH\_HEIGHT); ``` Set Text and Icons Text layers, use positions CSB\_TEXT\_LEFT, CSB\_TEXT\_RIGHT, CSB\_TEXT\_CENTER ``` char \*t = ...;custom\_status\_bar\_layer\_set\_text(custom\_status\_bar, CSB\_TEXT\_LEFT, t); ``` Icons, use positions CSB\_ICON\_0 to CSB\_ICON\_4 (positions are left to right, 0 indexed) ``` custom\_status\_bar\_layer\_set\_bitmap(custom\_status\_bar, CSB\_ICON\_4, your\_gbitmap); ``` When you're done, destroy ``` custom\_status\_bar\_layer\_destroy(custom\_status\_bar); ``` One last note! Your window should have a height of `168 - BAR_HEIGHT`. See the header file for more details. Enjoy! ![](http://rebootsramblings.ca/forumpictures/icons.png "Icons") ![](http://rebootsramblings.ca/forumpictures/textlayers.png "Text") --- # Effect Layer Source: https://developer.repebble.com/community/libraries/effect-layer/ EffectLayer is a visual effect layer for Pebble Smartwatch. User places the layer over screen at set coordinates and special effects are applied to that area of the screen. Effects work both on Aplite/Classic Pebble and Basalt/Pebble time unless specified (e.g. Blur works only on Basalt) To use the library place the source files in your SRC directory and add #include "effect\_layer.h" to your source. EffectLayer library is implemented in efficient pay-to-play way so only effects that you actually use get compiled into your binary. Currently supported effects: - Invert - Invert b/w only - Invert brightness, preserve hue - Vertical Mirror - Horizontal Mirror - Rotate 90 degrees (counter- or clock-wise) - Blur - Zoom - Lens - Mask - FPS - Shadow - Outline - effect\_colorize - effect\_colorswap ![Inverter](http://i.imgur.com/6t9r3qa.gif "Inverter") ![Bitmap Mask](http://i.imgur.com/JspSsx1.gif "Bitmap Mask") ![Text Mask](http://i.imgur.com/EdKu49w.png "Text Mask") --- # Gbitmap Colour Palette Manipulator Source: https://developer.repebble.com/community/libraries/gbitmap-colour-palette-manipulator/ This library was created in order to manipulate the color palettes of [gbitmaps](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") at runtime. It will be useful if you are trying to adapt your black and white pebble app and app icons for Pebble Time (colors). Instead of including resources for each color of an image/icon, you can just change its color palette with this library. The library includes a sample project which loads a picture and spits out its color palette. You can then decide which colors to replace. #### Before You Start. Ensure that your resources are of type `pbi8` or `png` in your `appinfo.json` file. Black and White images must be of type `pbi8` or they will be imported as 1 bit non paletized images. Resources of type `pbi` will not work since they do not have a color palette. ``` "resources": {"media": [{"file": "images/android\_bw.png","name": "IMAGE\_ANDROID","type": "pbi8"},{"file": "images/star.png","name": "IMAGE\_STAR","type": "png"}]} ``` The following library calls are available: ``` char\* get\_gbitmapformat\_text(GBitmapFormat format); ``` This function will return the GBitmapFormat text for the given GBitmapFormat. ``` char\* get\_gcolor\_text(GColor m\_color); ``` This function will return the text of the given GColor (ex: "GColorDukeBlue") ``` bool gbitmap\_color\_palette\_contains\_color(GColor m\_color, GBitmap \*im); ``` This function will return true if the provided gbitmap contains the specified color. False otherwise. ``` void spit\_gbitmap\_color\_palette(GBitmap \*im); ``` This function will spit out the number of colors in a gbitmap and will list which colors it contains. This is one of the most important functions in this library since you'll want to pass your gbitmap to it the first time in order to determine which colors it contains; which you'll use in the next function. ``` void gbitmap\_fill\_all\_except(GColor color\_to\_not\_change, GColor fill\_color, bool fill\_gcolorclear, GBitmap \*im, BitmapLayer \*bml); ``` This function will replace all colors in the gbitmap's palette except for the color your specify not to change. Tip: Very useful for filling the background color of a icon. I use this before setting a new icon color to clean up any stray colors, leaving only the background color and icon color. Pass NULL to \*bml if you do not want to update a BitmapLayer (useful for gbitmaps on your action bar). Allows you to set whether you want GColorClear to be replaced with the fill color. ``` void replace\_gbitmap\_color(GColor color\_to\_replace, GColor replace\_with\_color, GBitmap \*im, BitmapLayer \*bml); ``` This is function allows you to pass in a gbitmap, the color you want to replace and the target color. You also pass your BitmapLayer to this function so that it can automatically be marked dirty. This is an all in one function; it replaces the specified color and automatically updates the BitmapLayer. Pass NULL to \*bml if you do not want to update a BitmapLayer (useful for gbitmaps on your action bar). **Including the library in your project** - 1) Copy `gbitmap_color_palette_manipulator.c` and `gbitmap_color_palette_manipulator.h` into your project. - 2) Include the following at the top of your C file `#include "gbitmap_color_palette_manipulator.h"`. - 3) Use the functions you require. - 4) IMPORTANT: Comment out `#define SHOW_APP_LOGS` when deploying your production app. Otherwise, calls to the library will be slowed down by redundant text display function calls while the app is running on your user's devices. **Example uses** **Cleaning up Black and White images for better color manipulation.** This will set all palette entries to White except for the Black of your image/icon: - 1) Ensure that the Black and White image is of resource type `pbi8` in your `appinfo.json` file. - 2) Create the gbitmap from resource. - 3) Perform the following function call on the gbitmap `gbitmap_fill_all_except(GColorBlack, GColorWhite, true, your_gbitmap, NULL);` - 4) The gbitmap now has a palette that contains only Black (for the icon) and White (as the icon background). You can now manipulate the palette of this gbitmap more efficiently. Credits: I'd like to thank @gregoiresage and @ron064 for their contributions to the library. --- # Java Pebble Timeline Source: https://developer.repebble.com/community/libraries/java-pebble-timeline/ ## java-pebble-timeline This is a Java library for the Pebble timeline API. Documentation for the full API can be found [here](https://developer.repebble.com/guides/timeline/pin-structure/). ### Installation Using maven: ``` \\nl.palolem\\pebble-timeline\\1.1\\ ``` ### Usage ``` Pin pin = new Pin.Builder().id("example-pin-generic-1") .time(new Date()) .layout(new GenericPin.Builder() .title("News at 6 o'clock") .tinyIcon(Icon.NOTIFICATION_FLAG) .build()) .build(); Timeline.sendPin("my-token", pin); ``` See [tests](https://github.com/idserda/java-pebble-timeline/tree/master/src/test/java/nl/palolem/timeline) for more examples. --- # Js Message Queue Source: https://developer.repebble.com/community/libraries/js-message-queue/ PebbleKitJS library for sending messages to your Pebble app. ## Advantages Automatically retries sending messages when they fail, and ensures that messages are sent in order (i.e. wait for first message to be acknowledged before sending the second message. ## Usage 1. Include js-message-queue.min.js in your application's JS file, either by copying and pasting it into the top of `pebble-js-app.js`, or by adding it to your JS build script. 2. Replace all instances of `Pebble.sendAppMessage()` with `MessageQueue.sendAppMessage()`. 3. Profit! --- # Libraries Source: https://developer.repebble.com/community/libraries/ | Name | Creator | Language | License | | --- | --- | --- | --- | | [Color Selector](https://developer.repebble.com/community/libraries/color-select-lib.md) | Jason Brand | c | mit | | [Custom Status Bar for Pebble](https://developer.repebble.com/community/libraries/custom-status-bar-for-pebble.md) | Jonathan LaRocque (Reboot's Ramblings) | c | mit | | [EffectLayer](https://developer.repebble.com/community/libraries/effect-layer.md) | Yuriy Galanter, Ron64, Eric Arrigo, Grégoire Sage, mikalgow, Colin, Martin Norland | c | mit | | [GBitmap Colour Palette Manipulator](https://developer.repebble.com/community/libraries/gbitmap-colour-palette-manipulator.md) | Jonathan LaRocque (Reboot's Ramblings) | c | mit | | [Java Pebble Timeline](https://developer.repebble.com/community/libraries/java-pebble-timeline.md) | Jeroen Idserda | Java | mit | | [JS Message Queue](https://developer.repebble.com/community/libraries/js-message-queue.md) | Matthew Tole | js | mit | | [Pebble Timeline API for DotNet (C#)](https://developer.repebble.com/community/libraries/pebble-api-dotnet/) | Rob Chartier | c# | mit | | [Pebble API PHP](https://developer.repebble.com/community/libraries/pebble-api-php.md) | David Moreau | php | mit | | [PebbleTimeline API Ruby](https://developer.repebble.com/community/libraries/pebble-timeline-api-ruby.md) | Salim Hbeiliny | ruby | mit | | [PHPebbleTimeline](https://developer.repebble.com/community/libraries/php-pebble-timeline.md) | Matt Langlois | PHP | mit | | [PinPusher](https://developer.repebble.com/community/libraries/php-timeline-pinpusher.md) | Stephen Rees-Carter | PHP | mit | | [pypebbleapi](https://developer.repebble.com/community/libraries/pypebbleapi.md) | Alessio Bogon | python | mit | | [Simple Dithering Library](https://developer.repebble.com/community/libraries/simple-dithering-library.md) | Mathew Reiss | c | mit | | [T3 Keyboard](https://developer.repebble.com/community/libraries/t3-keyboard.md) | Chris Nucci | c | apache | | [ToastLayer Library](https://developer.repebble.com/community/libraries/toastlayer.md) | Chris Lewis | c | mit | --- # Pebble Api Php Source: https://developer.repebble.com/community/libraries/pebble-api-php/ Library for interacting with Pebble Timeline Pin from a server running php. **Work In Progress:** This project is currently under development, basic features are already functional. Feel free to contribute. ## Advantages Tested, object-oriented, leveraging Guzzle. ## Usage ``` php composer.phar require dav-m85/pebble-api-php:dev-master ``` ``` $client = new PebbleApi\Client();// Create (or update) a pin$pin = new PebbleApi\Pin("reservation-1395203", array( "id" =\> "reservation-1395203", "time" =\> "2014-03-07T09:01:10.229Z", "layout" =\> array( ... )));$user = new PebbleApi\User($userToken);$client-\>put($user, $pin);// Delete a pin$client-\>delete($user, $pin);// Create a pin for all users (shared pin)$sharedTopic = new PebbleApi\SharedTopic($apiToken, array('baseball', 'giants'));$client-\>put($sharedTopic, $pin); ``` --- # Pebble Timeline Api Ruby Source: https://developer.repebble.com/community/libraries/pebble-timeline-api-ruby/ A Ruby wrapper for the Pebble Timeline API This is currently a work in progress and contributions are welcome. It currently offers the ability to push/delete/update shared pins or user pins. ## Installation Add this line to your application's Gemfile: ``` gem 'pebble\_timeline' ``` And then execute: ``` $ bundle ``` Or install it yourself as: ``` $ gem install pebble_timeline ``` ## Usage ``` require 'pebble\_timeline'api = PebbleTimeline::API.new(ENV['PEBBLE\_TIMELINE\_API\_KEY'])# Shared pinspins = PebbleTimeline::Pins.new(api)pins.create(id: "test-1", topics: 'test', time: "2015-06-10T08:01:10.229Z", layout: { type: 'genericPin', title: 'test 1' })pins.delete("test-1")# User pinsuser\_pins = PebbleTimeline::Pins.new(api, 'user', USER\_TOKEN)user\_pins.create(id: "test-1", time: "2015-06-12T16:42:00Z", layout: { type: 'genericPin', title: 'test 1' })user\_pins.delete("test-1") ``` --- # Php Pebble Timeline Source: https://developer.repebble.com/community/libraries/php-pebble-timeline/ PHPebbleTimeline is a library for making use the Timeline API through PHP. ## Setup Copy the TimelineAPI Folder to your project's directory and include the required files. ## Documentation See [https://github.com/fletchto99/PHPebbleTimeline/blob/master/Readme.md](https://github.com/fletchto99/PHPebbleTimeline/blob/master/Readme.md) for indepth documentation regarding the API. ## Sample ``` //Include the timeline APIrequire\_once 'TimelineAPI/Timeline.php';//Import the required classesuse TimelineAPI\Pin;use TimelineAPI\PinLayout;use TimelineAPI\PinLayoutType;use TimelineAPI\PinIcon;use TimelineAPI\PinReminder;use TimelineAPI\Timeline;//Create some layouts which our pin will use$reminderlayout = new PinLayout(PinLayoutType::GENERIC\_REMINDER, 'Sample reminder!', null, null, null, PinIcon::NOTIFICATION\_FLAG);$pinlayout = new PinLayout(PinLayoutType::GENERIC\_PIN, 'Our title', null, null, null, PinIcon::NOTIFICATION\_FLAG);//Create a reminder which our pin will push before the event$reminder = new PinReminder($reminderlayout, (new DateTime('now')) -\> add(new DateInterval('PT10M')));//Create the pin$pin = new Pin('\', (new DateTime('now')) -\> add(new DateInterval('PT5M')), $pinlayout);//Attach the reminder$pin -\> addReminder($reminder);//Push the pin to the timelineTimeline::pushPin('sample-userToken', $pin); ``` --- # Php Timeline Pinpusher Source: https://developer.repebble.com/community/libraries/php-timeline-pinpusher/ PinPusher is a simple Pebble Timeline Pin API wrapper for PHP, with full support for all of the Pebble Timeline API options. It is designed to be easy to use, and allow you to completely construct Timeline pins using PHP classes. This is beneficial to those with bad memories and/or IDEs that autocomplete. It also covers each individual layout case with the different requirements, so you don't need to guess what works where. PinPusher is easily installable via Composer (or manually) and is framework independent. It also supports a PSR-3 compatible logger, if you need logging support. (Which is very helpful for debugging responses from the timeline API.) ## Installation PinPusher can be installed with [composer](https://getcomposer.org/): ``` composer require valorin/pinpusher ``` ## Examples Here are a couple of usage examples, based on the examples in the [Pebble timeline documentation](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md). More usage examples can be found at: [https://github.com/valorin/pinpusher](https://github.com/valorin/pinpusher). **Minimal Pin Example** ``` {"id": "example-pin-generic-1","time": "2015-03-19T18:00:00Z","layout": {"type": "genericPin","title": "News at 6 o'clock","tinyIcon": "system://images/NOTIFICATION\_FLAG"}} ``` With PinPusher: ``` use Valorin\PinPusher\Pusher;use Valorin\PinPusher\Pin;$pin = new Pin( 'example-pin-generic-1', new DateTime('2015-03-19T18:00:00Z'), new Pin\Layout\Generic( "News at 6 o'clock", Pin\Icon::NOTIFICATION\_FLAG ));$pusher = new Pusher()$pusher-\>pushToUser($userToken, $pin); ``` **Complete Pin Example** ``` {"id": "meeting-453923","time": "2015-03-19T15:00:00Z","duration": 60,"createNotification": {"layout": {"type": "genericNotification","title": "New Item","tinyIcon": "system://images/NOTIFICATION\_FLAG","body": "A new appointment has been added to your calendar at 4pm."}},"updateNotification": {"time": "2015-03-19T16:00:00Z","layout": {"type": "genericNotification","tinyIcon": "system://images/NOTIFICATION\_FLAG","title": "Reminder","body": "The meeting has been rescheduled to 4pm."}},"layout": {"title": "Client Meeting","type": "genericPin","tinyIcon": "system://images/TIMELINE\_CALENDAR","body": "Meeting in Kepler at 4:00pm. Topic: discuss pizza toppings for party."},"reminders": [{"time": "2015-03-19T14:45:00Z","layout": {"type": "genericReminder","tinyIcon": "system://images/TIMELINE\_CALENDAR","title": "Meeting in 15 minutes"}},{"time": "2015-03-19T14:55:00Z","layout": {"type": "genericReminder","tinyIcon": "system://images/TIMELINE\_CALENDAR","title": "Meeting in 5 minutes"}}],"actions": [{"title": "View Schedule","type": "openWatchApp","launchCode": 15},{"title": "Show Directions","type": "openWatchApp","launchCode": 22}]} ``` With PinPusher: ``` use Valorin\PinPusher\Pusher;use Valorin\PinPusher\Pin;// Generic Layout$layout = new Pin\Layout\Generic('Client Meeting', Pin\Icon::TIMELINE\_CALENDAR);$layout-\>setBody('Meeting in Kepler at 4:00pm. Topic: discuss pizza toppings for party.');// Create Notification$createNotification = new Pin\Notification\Generic('New Item', Pin\Icon::NOTIFICATION\_FLAG);$createNotification-\>setBody('A new appointment has been added to your calendar at 4pm.');// Update Notification$updateNotification = new Pin\Notification\Generic('Reminder', Pin\Icon::NOTIFICATION\_FLAG);$updateNotification-\>setTime(new DateTime('2015-03-19T16:00:00Z')) -\>setBody('The meeting has been rescheduled to 4pm.');// Reminders$reminders = [new Pin\Reminder\Generic(new DateTime('2015-03-19T14:45:00Z'), 'Meeting in 15 minutes', Pin\Icon::TIMELINE\_CALENDAR), new Pin\Reminder\Generic(new DateTime('2015-03-19T14:55:00Z'), 'Meeting in 5 minutes', Pin\Icon::TIMELINE\_CALENDAR),];// Actions$actions = [new Pin\Action\OpenWatchApp('View Schedule', 15), new Pin\Action\OpenWatchApp('Show Directions', 22),];// Put it all together$pin = new Pin('meeting-453923', new DateTime('2015-03-19T15:00:00Z'), $layout);$pin-\>setDuration(60) -\>setCreateNotification($createNotification) -\>setUpdateNotification($updateNotification) -\>setReminders($reminders) -\>setActions($actions);$pusher = new Pusher()$pusher-\>pushToUser($userToken, $pin); ``` --- # Pypebbleapi Source: https://developer.repebble.com/community/libraries/pypebbleapi/ Python library to interact with the Pebble Timeline APIs. It supports Python 2.7, 3.3 and 3.4. This is basically a port of the [pebble-api](https://www.npmjs.com/package/pebble-api) library for NodeJS. ## Installation Just use `pip`: ``` pip install pypebbleapi ``` ## Usage This snippet shows the basic usage: ``` from pypebbleapi importTimeline,Pinimport datetimetimeline=Timeline(my\_api\_key)my\_pin=Pin(id='123',datetime.date.today().isoformat())timeline.send\_shared\_pin(['a\_topic','another\_topic'],my\_pin) ``` --- # Simple Dithering Library Source: https://developer.repebble.com/community/libraries/simple-dithering-library/ **UPDATE** - _June 17, 2015_ ## Now supports the full range of RGB colors (255x255x255)! Check out [this forum post](https://forums.getpebble.com/discussion/comment/144191/#Comment_144191) for more info. This library provides easy functions for creating dithered rectangles and circles, using either random or ordered dithering. ![](https://camo.githubusercontent.com/22eff63e7c2cd377f51b9348fef2d86ac7177c58/687474703a2f2f63643862613062343461313563313030363566642d32343436316633393165323062373333363333316435373839303738616635332e7232332e6366312e7261636b63646e2e636f6d2f706562626c652e76616e696c6c61666f72756d732e636f6d2f656469746f722f73352f31623432786c3773656d65372e676966) ![](https://camo.githubusercontent.com/a97a768b6177020d2a9edc1f7fce2109230d5c88/687474703a2f2f63643862613062343461313563313030363566642d32343436316633393165323062373333363333316435373839303738616635332e7232332e6366312e7261636b63646e2e636f6d2f706562626c652e76616e696c6c61666f72756d732e636f6d2f656469746f722f6a652f717a717a763533733862326c2e706e67) By using 50% dithering, developers are able to combine every color available in Basalt, resulting in a new color palette of about 343 different colors. This is possible since 50% dithering is so small that the human eye perceives this as a single color. ![](http://cd8ba0b44a15c10065fd-24461f391e20b7336331d5789078af53.r23.cf1.rackcdn.com/pebble.vanillaforums.com/editor/3g/fzivcubrgeod.png) There are also functions for creating gradients, as well as animated transitions between two colors. ![](https://camo.githubusercontent.com/d768dc60336faf9902925e7741526dabc31dd288/687474703a2f2f63643862613062343461313563313030363566642d32343436316633393165323062373333363333316435373839303738616635332e7232332e6366312e7261636b63646e2e636f6d2f706562626c652e76616e696c6c61666f72756d732e636f6d2f656469746f722f756a2f6a78627775636f6b39357a732e676966) --- # T3 Keyboard Source: https://developer.repebble.com/community/libraries/t3-keyboard/ # Advantages The T3 Keyboard provides a fast and intuitive method for textual input on the Pebble smartwatch. # Usage 1. Add T3Window.h and T3Window.c to your Pebble project. 2. Include T3Window.h on the file that uses the T3 Keyboard. 3. Define keyboard sets and layouts or use pre-defined ones. 4. Define a handler to get the inputted text. 5. Create the window and show it. 6. Profit! Get more details at the repo listed above. --- # Toastlayer Source: https://developer.repebble.com/community/libraries/toastlayer/ ## Description ToastLayer UI element for showing a time-limited notification within your app. Use just like a normal [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer"), such as [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") or [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer"). ## Instructions 1. Copy `ToastLayer.c` and `ToastLayer.h` into your project's `src` directory. 2. Include in your C file with `#include "ToastLayer.h"` 3. Create a ToastLayer with `toast_layer_create()`. 4. Pop up the toast notification when desired with `toast_layer_show()`. 5. The toast will slide out of view when the duration has elapsed. Use `toast_layer_hide()` to hide early. --- # Guidelines for Using Pebble Trademarks and Copyrights Source: https://developer.repebble.com/legal/pebble-trademark/ ## tl;dr > This is a brief summary of the [full guidelines](#official-guidelines), which you can read below. You may not use "Pebble" in the name of your app, company or service, except in a referential manner. For example, " **Awesome App for Pebble**" is acceptable, but " **Pebble Awesome**" is not. You may not use the Pebble logo or any other Pebble-owned graphic symbols or icons. * * * ## Official Guidelines These guidelines are for Pebble licensees, authorized resellers, developers, customers, and other parties wishing to use Pebble’s trademarks, service marks or images in promotional, advertising, instructional, or reference materials, or on their web sites, products, labels, or packaging. Use of any Pebble logo for commercial purposes without the prior written consent of Pebble may constitute trademark infringement and unfair competition in violation of federal and state laws. Use of Pebble trademarks may be prohibited, unless expressly authorized. If you are a licensee of a Pebble trademark or logo and have been provided with special trademark usage guidelines with your license agreement, please follow those guidelines. If your license agreement does not provide usage guidelines, then follow these guidelines. If you are a Pebble Authorized Reseller or member of a Pebble program, you may be subject to additional restrictions. Pebble’s trademarks, service marks, trade names, and trade dress are valuable assets. In following these guidelines, you help us protect our valuable trademark rights and strengthen our corporate and brand identities. By using a Pebble trademark, in whole or in part, you are acknowledging that Pebble is the sole owner of the trademark and promising that you will not interfere with Pebble’s rights in the trademark, including challenging Pebble’s use, registration of, or application to register such trademark, alone or in combination with other words, anywhere in the world, and that you will not harm, misuse, or bring into disrepute any Pebble trademark. The goodwill derived from using any part of a Pebble trademark exclusively inures to the benefit of and belongs to Pebble. Except for the limited right to use as expressly permitted under these Guidelines, no other rights of any kind are granted hereunder, by implication or otherwise. If you have any questions regarding these guidelines, submit your query to Core Devices through the [contact page](https://developer.repebble.com/contact.md). ### Authorized Use of Pebble Trademarks 1. **1.** **Advertising, Promotional, and Sales Materials:** Only Pebble and its authorized resellers and licensees may use a Pebble logo in advertising, promotional, and sales materials. Such authorized parties may use a Pebble logo only as specified in their agreement with Pebble and any associated Guidelines and such use must always be in conjunction with the appropriate terms that define the relationship authorized by their contract with Pebble. 2. **2.** **Compatibility:** Developers may use Pebble or any other Pebble word mark (but not a Pebble logo or other Pebble-owned graphic symbol) in a **referential phrase** on packaging or promotional/advertising materials to describe that the third party product is compatible with the referenced Pebble product or technology, provided they comply with the following requirements: 3. **2.1** The Pebble word mark is not part of the product name. 4. **2.2** The Pebble word mark is used in a referential phrase such as “runs on,” “for use with,” “for,” or “compatible with.” 5. **2.3** The Pebble word mark appears less prominent than the product name. 6. **2.4** The product is in fact compatible with, or otherwise works with, the referenced Pebble product. 7. **2.5** The reference to Pebble does not create a sense of endorsement, sponsorship, or false association with Pebble or Pebble products or services. 8. **2.6** The use does not show Pebble or its products in a false or derogatory light. 9. **3.** **Publications, Seminars, and Conferences:** You may use a Pebble word mark in connection with book titles, magazines, periodicals, seminars, or conferences provided you comply with the following requirements: 10. **3.1** The use is referential and less prominent than the rest of the title. Acceptable: XYZ CONFERENCE for Pebble Users. 11. **3.2** The use reflects favorably on both Pebble and Pebble products or technology. 12. **3.3** Your name and logo appear more prominent than the Pebble word mark on all printed materials related to the publication, seminar or conference. 13. **3.4** Any Pebble-owned graphic symbol, logo, icon or image does not appear on or in the publication or on any materials related to the publication, seminar, or conference without express written permission from Pebble. 14. **3.5** A disclaimer of sponsorship, affiliation, or endorsement by Pebble, similar to the following, is included on the publication and on all related printed materials: “(Title) is an independent (publication) and has not been authorized, sponsored, or otherwise approved by Core Devices LLC.” 15. **3.6** A trademark attribution notice is included in the credit section giving notice of Pebble’s ownership of its trademark(s). Please refer to the section below titled “Proper Trademark Notice and Attribution.” 16. **4.** **Web Sites:** Web sites that serve only as noncommercial electronic informational forums concerning a Pebble product or technology may use the appropriate Pebble word mark, provided such use complies with the guidelines set forth in Section 3 above. ### Unauthorized Use of Pebble Trademarks 1. **1.** **Company, Product, or Service Name:** You may not use or register, in whole or in part, Pebble or any other Pebble trademark, including Pebble-owned graphic symbols, logos, icons, or an alteration thereof, as or as part of a company name, trade name, product name, or service name except as specifically noted in these guidelines. 2. **2.** **Pebble’s Logos and Pebble-owned Graphic Symbols:** You may not use a Pebble logo or any other Pebble-owned graphic symbol or icon on or in connection with web sites, products, packaging, manuals, promotional/advertising materials, or for any other purpose except pursuant to an express written trademark license from Pebble, such as a reseller agreement. 3. **3.** **Variations, Takeoffs or Abbreviations:** You may not use a variation of a Pebble logo for any purpose. Third parties cannot use a variation, phonetic equivalent, foreign language equivalent, takeoff, or abbreviation of a Pebble trademark for any purpose. For example: Not acceptable: - Pebbletree - Pebble Cart - PebMart - RunPeb - SoccerPebble 1. **4.** **Disparaging Manner:** You may not use a Pebble trademark or any other Pebble-owned graphic symbol, logo, or icon in a disparaging manner. 2. **5.** **Endorsement or Sponsorship:** You may not use Pebble or any other Pebble trademark, including Pebble-owned graphic symbols/logos, or icons, in a manner that would imply Pebble’s affiliation with or endorsement, sponsorship, or support of a third party product or service. 3. **6.** **Merchandise Items:** You may not manufacture, sell or give-away merchandise items, such as T-shirts and mugs, bearing Pebble or any other Pebble trademark, including symbols, logos, or icons, except pursuant to an express written trademark license from Pebble. 4. **7.** **Pebble’s Trade Dress:** You may not imitate the distinctive Pebble packaging, web site design, logos, or typefaces. 5. **8.** **Slogans and Taglines:** You may not use or imitate a Pebble slogan or tagline. For example: “Tiny Moments of Awesome.” 6. **9.** **Domain Names:** You may not use an identical or virtually identical Pebble trademark as a second level domain name. Not acceptable: - “pebble.com” - “peble.com” - “pebblee.com” ### Rules for Proper Use of Pebble Trademarks 1. **1.** Trademarks are adjectives used to modify nouns; the noun is the generic name of a product or service. 2. **2.** As adjectives, trademarks may not be used in the plural or possessive form. Correct: I bought two Pebble smartwatches. Not Correct: I bought two Pebbles. 1. **3.** An appropriate generic term must appear after the trademark the first time it appears in a printed piece, and as often as is reasonable after that. 2. **4.** Always spell and capitalize Pebble’s trademarks in accordance with their registration. Do not shorten or abbreviate Pebble product names. Do not make up names that contain Pebble trademarks. ### Proper Trademark Notice and Attribution 1. **1.** **Distribution Within the United States Only:** 2. **1.1** On product, product documentation, or other product communications that will be distributed only in the United States, use the appropriate trademark symbol (TM, SM, ®) the first time the Pebble trademark appears in the text of the advertisement, brochure, or other material. 3. **1.2** Properly display the correct trademark symbol, spelling of the trademark, and generic term to use with the trademark. 4. **1.3** Include an attribution of Pebble’s ownership of its trademarks within the credit notice section of your product, product documentation, or other product communication. Following are the correct formats: 5. **2.** **Distribution Outside the United States:** 6. **2.1** Do not use trademark symbols on products, product documentation, or other product communications that will be distributed outside the United States. 7. **2.2** Use one of the following international credit notices: \_\_\_\_\_\_\_\_\_ is a trademark of Core Devices LLC, registered in the U.S. and other countries. \_\_\_\_\_\_\_\_\_ is a trademark of Core Devices LLC ### Depictions of Pebble Products 1. **1.** **Endorsement or Sponsorship:** Pebble does not support the use of its logos, company names, product names, or images of Pebble products by other parties in marketing, promotional or advertising materials as their use may create the perception that Pebble endorses or sponsors the product, service or promotion. 2. **2.** **Compatibility:** If you are a developer, you may show an image of a Pebble product in your promotional/advertising materials to depict that your product is compatible with, or otherwise works with, the Pebble product or technology, provided you comply with the following requirements: 3. **2.1** Your product is in fact compatible with, or otherwise works with, the referenced Pebble product. 4. **2.2** The image is an actual photograph of the genuine Pebble product and not an artist’s rendering (Note: You must obtain express written permission from Pebble before using any photograph owned or licensed by Pebble). 5. **2.3** The Pebble product is shown only in the best light, in a manner or context that reflects favorably on the Pebble products and on Core Devices LLC. 6. **2.4** The reference to Pebble does not create a sense of endorsement or sponsorship by, or other false association with, Pebble or Pebble products. For further information with respect to Pebble’s copyrights, please submit your request in writing through the [contact page](https://developer.repebble.com/contact.md). --- # More Source: https://developer.repebble.com/more/ # More ### [Examples](https://github.com/pebble-examples) A link to the Pebble Examples organisation on GitHub. ### [Contact Form](https://developer.repebble.com/contact.md) A form where you can contact us. ### [Legal Agreements](https://developer.repebble.com/legal.md) Some of the developer-related Pebble legal agreements that cover your usage of this site, the Developer Dashboard, and the Pebble SDK. ### [Frequently Asked Questions](https://developer.repebble.com/faqs.md) A collection of some of the questions that are most frequently asked by developers. ### [Open Source](https://developer.repebble.com/open-source.md) Check out our open source projects and libraries. --- # Contributing to the site Source: https://developer.repebble.com/contributing/ # Contributing to the site The new Pebble developer site is open sourced on GitHub. We welcome your contributions to improve the site. #### [https://github.com/coredevices/sdk-docs](https://github.com/coredevices/sdk-docs) --- # Pebble Developer Distribution Agreement Source: https://developer.repebble.com/legal/distribution/ **THIS AGREEMENT CONTAINS A BINDING ARBITRATION PROVISION AND TIME LIMIT ON SUBMITTING CLAIMS THAT AFFECT YOUR RIGHTS. PLEASE READ THESE TERMS AND CONDITIONS CAREFULLY.** ## 1. Definitions. 1. **1.1.** **"Agreement"** means this Pebble Developer Distribution Agreement. 2. **1.2.** **"Brand Features"** means the trade names, trademarks, service marks, logos, domain names, and other distinctive brand features of each party, respectively, as owned (or licensed) by such party from time to time. 3. **1.3.** **"Companion Apps"** means software applications for Companion Devices that connect to and communicate with Devices through the Pebble Mobile App(s). 4. **1.4.** **"Companion Devices"** means Android and iOS mobile devices that can connect to and communicate with Devices via the Pebble Mobile App(s) and/or Companion Apps. 5. **1.5.** **"Developer Account"** means your registered developer account that is required for you to be able to distribute Products via the Pebble Mobile App(s). 6. **1.6.** **"Devices"** means an authorized Pebble smartwatch or other device made, distributed or licensed by Pebble to use the Pebble Platform. 7. **1.7.** **"Pebble"** means Core Devices LLC. 8. **1.8.** **"Pebble Mobile App(s)"** means the mobile applications made available by Pebble for use with Android or iOS mobile devices that provide a marketplace that enables users to search for Products to download to use with Devices and with Companion Devices. 9. **1.9.** **"Products"** means software applications and related digital materials that are distributed via the Pebble Mobile App(s). Products may be applications for use on a Device and/or may be Companion Apps. 10. **1.10.** **"Pebble Platform"** means the Pebble hardware mobile device firmware and software application platform. ## 2. Agreement Formation. 1. **2.1.** **Acceptance of this Agreement.** You must accept this Agreement before you distribute or install your Products through the Pebble Mobile App(s). By submitting any Product for distribution via the Pebble Mobile App(s), or by clicking to accept this Agreement, you hereby agree to the terms and conditions of this Agreement. Do not accept this Agreement or submit any Products for distribution via the Pebble Mobile App(s) if you or your company is barred from use of the Pebble Mobile App(s) or the distribution of your Products under the laws or regulations of the United States or any other country. 2. **2.2.** **Authority.** You represent and warrant that you are of the legal age of majority in the country in which you reside (typically 18) or that you have your parent or legal guardian's permission to agree to these terms. If you are agreeing to be bound by this Agreement on behalf of your employer or another entity, you represent and warrant that you have full legal authority to bind your employer or such entity to this Agreement. If you do not have such legal authority, you may not accept this Agreement or submit Products for the Pebble Mobile App(s) on behalf of your employer or any other entity. ## 3. License Grants. 1. **3.1.** You grant to Pebble a non-exclusive, worldwide and royalty-free license to: (a) copy, perform, display and use the Products for administrative and demonstration purposes in connection with the operation and marketing of the Pebble Mobile App(s); and (b) use the Products to make improvements to the Pebble Platform. 2. **3.2.** You grant to Pebble a non-exclusive and royalty-free license to distribute the Products according to any publishing options selected by you on the Product upload for the Pebble Mobile App(s). You further grant Pebble a non- exclusive and royalty-free license to copy the JavaScript included in your Product and store that code in a central location in the iOS version of the Smartwatch App for review and approval by Apple Corporation. 3. **3.3.** Pebble may use consultants and other contractors in connection with the performance of its obligations and to exercise its rights under this Agreement, provided that such consultants and contractors are subject to the same obligations as Pebble. After termination of this Agreement, Pebble will not distribute your Product, but may retain and use copies of the Product for support purposes. 4. **3.4.** You grant to the user a non-exclusive, worldwide and perpetual license to perform, display and use the Product on the Devices and/or Companion Devices, as applicable. If you choose, you may include a separate end user license agreement in your Product that will govern the user's rights to the Product in lieu of the previous sentence. 5. **3.5.** You represent and warrant that you have all intellectual property rights, including all necessary patent, trademark, trade secret, copyright or other proprietary rights, in and to your Products. If you use third-party materials, you represent and warrant that you have the right to distribute the third- party materials in your Products. You agree that you will not submit material to Pebble that is copyrighted, protected by trade secret or otherwise subject to third-party proprietary rights, including patent, privacy and publicity rights, unless you are the owner of such rights or have sufficient permission from their rightful owner to submit the material in accordance with this Agreement. ## 4. Brand Features and Publicity. 1. **4.1.** **Brand feature Ownership and License.** Each party will own all right, title and interest (including, without limitation, all intellectual property rights) in and to its Brand Features. Except to the limited extent expressly provided in this Agreement, neither party grants, nor will the other party acquire, any right, title or interest (including, without limitation, any implied license) in or to any Brand Features of the other party. Subject to the terms and conditions of this Agreement, you grant to Pebble a limited, non-exclusive license during the term of this Agreement to display your Brand Features, submitted by you to Pebble, for use in connection with the distribution and sale of your Products through the Pebble Mobile App(s) and/or to otherwise fulfill Pebble's obligations under this Agreement. If you discontinue the distribution of specific Products on the Pebble Mobile App(s), Pebble will cease use of the discontinued Products' Brand Features pursuant to this Section 4.1, except as necessary to allow Pebble to facilitate any reinstalls for users who downloaded the Products prior to the date you removed them from the Pebble Mobile App(s). 2. **4.2.** **No Confusion.** Unless you have received Pebble's express written permission, you will not otherwise use any trademark, service mark, commercial symbol or other proprietary right of Pebble as part of the title for any of your Products. 3. **4.3.** **Publicity.** In addition to the license granted in 4.1 above, for purposes of marketing the presence and distribution of the your Products via the Pebble Mobile App(s), Pebble may include your Brand Features submitted by you to Pebble: (a) within the Pebble Mobile App(s) and in any Pebble-owned online or mobile properties; (b) in other online or mobile communications; (c) in presentations; and (d) in customer lists, which appear either online or on mobile devices (which includes, without limitation, customer lists posted on Pebble websites). If you discontinue the distribution of specific Products via the Pebble Mobile App(s), Pebble will cease use of the discontinued Products' Brand Features for such marketing purposes within a reasonable period of time. Pebble grants to you a limited, non-exclusive, worldwide, royalty-free license to use the Pebble Brand Features for the term of this Agreement solely for marketing of your Products and only in accordance with the Pebble Brand Guidelines, which can be found at [https://developer.repebble.com/legal/pebble-trademark/](https://developer.repebble.com/legal/pebble-trademark.md). ## 5. Your Use of the Pebble Mobile App(s) Market. 1. **5.1.** **Limited License.** Except for the license rights granted by you in Sections 3 and 4 above, Pebble agrees that it obtains no right, title or interest from you (or your licensors) under this Agreement in or to any of your Products, including any intellectual property rights in those Products. 2. **5.2.** **Use of Apps.** You agree to use the Pebble Mobile App(s) and related market only for purposes that are permitted by: (a) this Agreement; and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions in which your Products are distributed or used (including any laws regarding the export of data or software to and from the United States or other relevant countries). 3. **5.3.** **Protection of Users.** You agree that if you use the Pebble Mobile App(s) to distribute your Products, you will protect the privacy and legal rights of your users, prominently display a privacy policy that describes to your users the information that is collected by you and your Products and how such information is used and shared. You will obtain all required consents from your users regarding the collection, use and disclosure of your users' personal information. You will maintain and process all user data in accordance with your privacy policy and all applicable laws and regulations in any countries in which your Products are distributed or used. 4. **5.4.** **Prohibited Actions.** You agree that you will not engage in any activity with the Pebble Mobile App(s) and/or the related market, including the development or distribution of Products, which interferes with, disrupts, damages or accesses in an unauthorized manner the devices, servers, networks or other properties or services of any third party, including, but not limited to, those related to the providers of the Companion Devices and their users, Pebble or any mobile network operator. 5. **5.5.** **Non-Compete.** You may not use the Pebble Mobile App(s) to distribute or make available any Product whose primary purpose is to facilitate the distribution of software applications for Android and/or iOS mobile devices that do not make use of, do not interact with and/or are unrelated to the Devices. 6. **5.6.** **Sole Responsibility.** You agree that you are solely responsible for (and that Pebble has no responsibility to you or to any third party for) any Products you distribute through the Pebble Mobile App(s) and for the consequences of your actions (including any loss or damage that Pebble may suffer as a result). You agree that you are solely responsible for (and that Pebble has no responsibility to you or to any third party for) any breach of your obligations under this Agreement, any applicable third-party contract or terms of service, or any applicable law or regulation and for the consequences (including any loss or damage which Pebble or any third party may suffer) of any such breach. 7. **5.7.** **Product Ratings.** The Pebble Mobile App(s) and the related web pages offered by Pebble will allow users to rate Products. Product ratings will be used to determine the placement of Products on the Pebble Mobile App(s), with higher- rated Products generally given better placement, subject to Pebble's ability to change such placement in Pebble's sole discretion. The Pebble Mobile App(s) may also assign you a composite score for any Product that has not received user ratings. A composite score will be a representation of the quality of your Product based on your history and will be determined in Pebble's sole discretion. For new developers without Product history, Pebble may use or publish performance measurements, such as uninstall rates, to identify or remove Products that are not meeting acceptable standards, as determined by Pebble in Pebble's sole discretion. Pebble reserves the right to display Products to users in a manner that will be determined in Pebble's sole discretion. Your Products may be subject to user ratings to which you may not agree. You may contact Pebble if you have any questions or concerns regarding such ratings. 8. **5.8.** **Uploading Products to Pebble Mobile App(s).** You will be responsible for uploading your Products to the Pebble Mobile App(s), providing required Product information to users and accurately disclosing the security permissions necessary for the Product to function on user Devices and/or Companion Devices, as applicable. Products that are not properly uploaded will not be published in the Pebble Mobile App(s). 9. **5.9.** **Restricted Content.** Any Product you distribute on the Pebble Mobile App(s) must adhere to the Developer Program Policies, which can be found at [https://play.google/developer-content-policy/](https://play.google/developer-content-policy/). ## 6. Product Takedowns. 1. **6.1.** **Your Takedowns.** You may remove your Products from future distribution via the Pebble Mobile App(s) at any time, but you must comply with this Agreement for any Products distributed through the Pebble Mobile App(s) prior to removal. Removing your Products from future distribution does not: (a) affect the license rights of users who have previously downloaded your Products; (b) remove your Products from Devices or from any part of the location where previously purchased or downloaded applications are stored by Pebble on behalf of users; or (c) change your obligation to deliver or support Products or services that have been previously downloaded by users. 2. **6.2.** **Pebble Takedowns.** While Pebble does not undertake any obligations to monitor the Products or their content, if Pebble is notified by you or otherwise becomes aware and determines in its sole discretion that a Product, or any portion thereof, or your Brand Features: (a) violates the intellectual property rights or any other rights of any third party; (b) violates any applicable law or is subject to an injunction; (c) is pornographic, obscene or otherwise violates Pebble's hosting policies or other terms of service as may be updated by Pebble from time to time in its sole discretion; (d) is being distributed by you improperly; (e) may create liability for Pebble or a third party app store provider; (f) is deemed by Pebble to have a virus or is deemed to be malware, spyware or have an adverse impact on Pebble's or a third-party communication carrier's network; (g) violates the terms of this Agreement; or (h) display of the Product is impacting the integrity of Pebble's servers (i.e., users are unable to access such content or otherwise experience difficulty), Pebble may remove the Product from the Pebble Mobile App(s) or reclassify the Product in its sole discretion. Pebble reserves the right to suspend and/or bar any developer and the related Products from the Pebble Mobile App(s) in its sole discretion. Pebble enters into distribution agreements with device manufacturers and communication carriers to place the Pebble Mobile App(s) on the app stores for Companion Devices. These distribution agreements may require the involuntary removal of Products in violation of the Companion Device manufacturer's or communication carrier's terms of service. ## 7. Privacy. Pebble may collect certain usage statistics from the Devices using your Products, including, but not limited to, information of how the Products and Devices are being used. This information will be collected and used in accordance with the Pebble Privacy Policy, which can be found at [https://rePebble.com/legal/privacy/](https://rePebble.com/legal/privacy/). ## 8. Fees and Payments. At this time, Pebble does not charge any fees to upload a Product for distribution via the Pebble Mobile App(s) and does not have in place any mechanism to enable you to charge any fees for the use of your Product. If this changes in the future, Pebble will update this Agreement to reflect the changes in accordance with the terms of Section 15. ## 9. Term and Termination. This Agreement is effective until terminated by Pebble or you as described below. 1. **9.1.** You may terminate this Agreement by providing Pebble with thirty (30) days' prior written notice, unless this Agreement otherwise terminates pursuant to Section 9.2. 2. **9.2.** Pebble may terminate this Agreement at any time if: (a) you have breached any provision of the this Agreement; (b) Pebble is legally required to do so; or (c) Pebble decides not to provide the Pebble Mobile App(s) or services related to the Pebble Mobile App(s) to users in the country in which you reside. 3. **9.3.** The provisions of Sections 1, 4.3, 7, 9.3, and 10 through 16 of this Agreement will survive any termination or expiration of this Agreement. ## 10. No Warranty. YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE PEBBLE MOBILE APP(S) IS AT YOUR SOLE RISK AND THAT THE PEBBLE MOBILE APP(S) ARE PROVIDED TO YOU ON AN "AS IS" BASIS AND WITHOUT WARRANTY OF ANY KIND. PEBBLE DISCLAIMS ALL WARRANTIES, STATUTORY, EXPRESS OR IMPLIED, RELATING TO THE SMARTWATCH APP(S), INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF NON-INFRINGEMENT OF THIRD PARTY RIGHTS, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. YOU AGREE THAT YOUR USE OF THE PEBBLE MOBILE APP(S) IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR ANY LOSS OF DATA THAT RESULTS FROM SUCH USE. YOU ACKNOWLEDGE AND AGREE THAT PEBBLE CANNOT ENSURE THAT PRODUCTS SUBMITTED BY OR ON BEHALF OF YOU WILL BE PROTECTED FROM THEFT OR MISUSE OR FREE OF VIRUSES OR OTHER DESTRUCTIVE FEATURES, AND THAT PEBBLE WILL HAVE NO LIABILITY ARISING FROM A FAILURE OF ANY SECURITY TECHNOLOGY OR PROCEDURE OR OF ANY USER TO COMPLY WITH ANY TERMS OF USE REGARDING THE PEBBLE MOBILE APP(S) OR OTHERWISE. ## 11. Limitation of Liability. 1. **11.1.** **No Liability for Consequential Damages.** TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT WILL PEBBLE BE LIABLE FOR ANY LOSS OF USE, INTERRUPTION OF BUSINESS, OR INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND (INCLUDING LOST PROFITS) ARISING OUT OF THE USE OF THE PEBBLE MOBILE APP(S) OR THE DISTRIBUTION AND USE OF YOUR PRODUCTS REGARDLESS OF THE FORM OF ACTION, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT PRODUCT LIABILITY OR OTHERWISE, EVEN IF PEBBLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 2. **11.2.** **Limitation on Direct Damages.** IN NO EVENT WILL PEBBLE'S AGGREGATE LIABILITY TO YOU UNDER THIS AGREEMENT EXCEED ONE HUNDRED DOLLARS (USD $100). 3. **11.3.** **Allocation of Risk.** You and Pebble agree that the foregoing warranty disclaimer and limitation of liability in Sections 10 and 11, respectively, fairly allocate the risks in this Agreement between the parties. You and Pebble further agree that this allocation is an essential element of the basis of the bargain between the parties, that Pebble would not have entered this Agreement without these limitations, and that the limitations will apply notwithstanding any failure of the essential purpose of this Agreement or any limited remedy hereunder. ## 12. Indemnification. To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Pebble, its affiliates and their respective directors, officers, employees, agents and representatives from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys' fees) arising out of or allegedly based (in whole or in part) on: (a) your use of the Pebble Mobile App(s); (b) your Product(s) (and any content contained therein); (c) any violation by you of any applicable law or regulation, including, without limitation, the violation of any privacy law or regulation; or (d) any non- compliance by you with the terms of this Agreement. ## 13. Export. You acknowledge and agree that you will not import, export, or re-export, directly or indirectly, any commodity (including, without limitation, the Product to any country in violation of the laws and regulations of any applicable jurisdiction. This restriction expressly includes, without limitation, the export regulations of the United States, and the import and export restrictions of the various European countries. You further agree to defend, indemnify and hold harmless Pebble, its affiliates and their respective directors, officers, employees, agents and representatives from any losses, costs, claims or other liabilities arising out of your breach of this Section 13. ## 14. Dispute Resolution and Binding Arbitration Provision. **Definitions.** As used in this Arbitration Provision, the terms **" Pebble ," "we," "us,"** and **"our"** refer to **Core Devices LLC** , including its subsidiaries and agents; the terms **" you"** and **" your"** refer to you as an individual as well as other individuals you allow to access or use the Products, and any legal entity you control, work for, or represent when you access or use the Products. The word " Products" means your access to and/or use of any Pebble website, advertisement or promotion and any product, service, or software that you obtain from or through Pebble. The word "Claims" means all claims, disputes, or controversies between you and us of any nature or kind, whether pre-existing, present, or future, that arise from or relate to the Products. This includes but is not limited to disagreements about the validity, enforceability, or scope of this Arbitration Provision. Informal Efforts to Resolve Dispute. If a dispute arises between you and Pebble, you should first attempt to resolve it by sending the details of your complaint, including your contact information for a response, through the [contact page](https://developer.repebble.com/contact.md). We will attempt in good faith to resolve all Claims submitted this way within fifteen (15) days of receipt. Agreement to Arbitrate; Right to Opt Out. If informal efforts to resolve Claims fail or are not used, you agree that any and all Claims will be resolved exclusively by binding arbitration as described herein, except that: (i)you may assert Claims in a small claims court in the United States if your Claims meet the court's jurisdictional requirements; and (ii) either party may pursue Claims and relief in a court of competent jurisdiction regarding the validity and/or infringement of a party's intellectual property rights. There is no judge or jury in arbitration, and court review of an arbitration award is very limited. However, an arbitrator can award to you on an individual basis the same damages and forms of relief as a court could (including injunctive and declaratory relief as well as statutory damages), and must follow the law and terms of this Agreement as a court would. Any arbitration under this Agreement will take place on an individual basis; class arbitrations and class actions and private attorney general actions are not permitted. **IF YOU DO NOT WISH TO BE BOUND BY THIS ARBITRATION PROVISION, YOU MUST NOTIFY PEBBLE IN WRITING WITHIN 30 DAYS FROM THE DATE THAT YOU FIRST ACCEPT OR HAVE ACCESS TO THIS AGREEMENT BY SUBMITTING AN OPT-OUT REQUEST THROUGH THE [CONTACT PAGE](https://developer.repebble.com/contact.md). YOUR WRITTEN NOTIFICATION MUST INCLUDE YOUR NAME, ADDRESS, the email address you used to register with PEBBLE, AND A clear STATEMENT THAT YOU DO NOT WISH TO RESOLVE DISPUTES WITH us THROUGH ARBITRATION. YOUR DECISION TO OPT OUT OF THIS ARBITRATION PROVISION WILL HAVE NO ADVERSE EFFECT ON YOUR RELATIONSHIP WITH us OR THE DELIVERY OF PRODUCTS TO YOU by us. IF YOU HAVE PREVIOUSLY NOTIFIED us OF YOUR DECISION TO OPT OUT OF ARBITRATION, YOU DO NOT NEED TO DO SO AGAIN.** **Arbitration Fees.** The allocation and payment of all filing, administration and arbitrator fees will be governed by the JAMS's rules which limit the amount a consumer is required to pay. If the arbitrator determines that your Claims are not frivolous applying the standards of the Federal Rules of Civil Procedure, we agree to reimburse you the amount of all filing, administration and arbitrator fees you are required to pay for the arbitration. **Arbitration Rules.** The arbitration will be conducted by the JAMS ("JAMS") under its rules if you are a resident of the United States; if your use of the Products has been principally for personal or household use, the JAMS' procedures for consumer-related disputes including the minimum fairness standards will also apply. If you are a resident of a country other than the United States, the arbitration will be conducted by the JAMS in San Francisco, California, under its rules for international arbitration, and you and we agree to submit to the personal jurisdiction of the U.S. federal court in San Francisco, California in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator. In the event of a conflict or inconsistency between the applicable arbitration rules and this Arbitration Provision, this Arbitration Provision shall govern and control. The arbitration will be conducted in the English language by a single arbitrator who is an attorney-at-law with at least fifteen years' experience in consumer and technology transactions and who is also a member of the JAMS roster of arbitrators. If you and we can't agree on a mutually acceptable arbitrator within fifteen (15) days after the arbitration is initiated, then the JAMS will pick a neutral arbitrator who meets the qualifications. The JAMS rules are available at [http://www.jamsadr.com](http://www.jamsadr.com) , or by calling 1- 800 – 352- 5267 from inside the United States or +44 207 583 9808 from outside the United States. **Initiating Arbitration.** To begin an arbitration proceeding, you must follow the procedures specified by the applicable JAMS rules as described on their website at [http://www.jamsadr.com](http://www.jamsadr.com). **Time Restriction.** YOU MUST FILE A COMPLAINT WITH JAMS OR A PERMITTED COURT WITHIN ONE (1) YEAR OF THE DATE OF THE OCCURRENCE OF THE EVENT OR FACTS GIVING RISE TO A CLAIM, OR YOU WAIVE THE RIGHT TO PURSUE ANY CLAIM BASED UPON SUCH EVENT, FACTS OR DISPUTE. **Arbitration Process.** Because appearing in person for arbitration can be unduly burdensome in the circumstances, arbitration under this Arbitration Provision shall not require any personal appearance by the parties or witnesses unless mutually agreed. Either or both parties may participate by written submissions, telephone calls, or other means of remote communication as allowed by the arbitrator. The arbitration proceedings will be conducted in the English language at a location designated by the JAMS that is the most convenient for you. The arbitration can only decide Claim(s) between you and us, and may not consolidate or join the claims of other persons that may have similar claims. There shall be no pre-arbitration discovery except as provided for in the applicable JAMS rules. The arbitrator will honor claims of privilege recognized by law and will take reasonable steps to protect customer account information and other confidential or proprietary information. In conducting the arbitration proceeding, the arbitrator will apply the law of the State of California (without regard to its conflicts of law provisions) including U.S. federal law for matters covered by federal law (_e.g._ the Federal Arbitration Act). The confidentiality provisions of this Agreement will be enforceable under the provisions of the California Uniform Trade Secrets Act, California Civil Code Section 3426, as amended. At the request of any party, the arbitrator shall provide a brief written explanation of the basis for the decision and award. Judgment upon the award rendered by the arbitrator may be entered in any court having jurisdiction. The arbitrator's decision will be final and binding on the parties except for any right to appeal under the JAMS rules or the Federal Arbitration Act. **Recovery and Attorneys' Fees.** If the arbitrator rules in your favor on the merits of any Claim you bring against us and issues you an award that is greater in monetary value than our last written settlement offer made to you before written submissions are made to the arbitrator, then we will (i) pay you 150% of your arbitration damages award, up to $1,000 over and above your damages award; and (ii) pay your attorneys, if any, the amount of attorneys' fees, and reimburse any expenses (including expert witness fees and costs) that you or your attorney reasonably incurred for investigating, preparing, and pursuing your Claim in arbitration. The arbitrator may make rulings and resolve disputes as to the payment and reimbursement of such fees, expenses, and the alternative payment and the attorney premium at any time during the proceeding and upon request from either party made within 14 days of the arbitrator's ruling on the merits. The right to attorneys' fees and expenses discussed above supplements any right to attorneys' fees and expenses you may have under applicable law, although you may not recover duplicative awards of attorneys' fees or costs. If your use of the Products was principally for personal or household use, neither party will be entitled to any award of punitive or special damages and Pebble waives any right it may have to seek an award of attorneys' fees and expenses from you in connection with any arbitration of Claims between us. **Confidentiality.** You and we shall keep confidential any information exchanged during the arbitration as well as the decision of the arbitrator made with respect to any Claim(s) arbitrated under this Arbitration Provision and, with the exception of disclosure to your or our attorneys, accountants, auditors, and other legal or financial advisors, neither party shall disclose such information or decision to any other person unless required to do so by law. **Continuing Obligation to Arbitrate; Severability.** This Arbitration Provision shall survive termination of your access to or use of any Products and related agreements. If any portion of this Arbitration Provision is deemed invalid or unenforceable at law, such invalid or unenforceable provision will be interpreted, construed or reformed to the extent required to make it valid and enforceable, and this shall not invalidate the remaining portions of this Arbitration Provision. **Contact:** Core Devices LLC, ATTN: **LEGAL/ARBITRATION** , through the [contact page](https://developer.repebble.com/contact.md). ## 15. Changes to the Agreement. Pebble may make changes to this Agreement at any time by sending you notice by email describing the modifications made. Pebble will also post a notification on the Pebble web site. The changes will become effective, and will be deemed accepted by you: (a) immediately for those who first register with a Pebble Developer Account after the notification is posted; or (b) upon your acceptance of the modified Agreement for pre-existing developers; provided, however, that changes required by law will be deemed to be accepted and effective upon any use by you of the Pebble Mobile App(s) to distribute your Products after Pebble posts notice of such change. In the event that you do not agree with the modifications to the Agreement within thirty (30) days after the date the email is sent, then your rights under this Agreement will terminate automatically, which termination will be your sole and exclusive remedy. ## 16. General Provisions. This Agreement is not assignable or transferable, in whole or in part, by you, whether involuntarily, by merger, operation of law or otherwise without Pebble's prior written consent. Any amalgamation or merger of a party with any third party, or the purchase or all or substantially all of the assets of a party, will be deemed an assignment requiring consent. Any attempted transfer in violation of this section is void. A waiver of any default hereunder or of any of the terms and conditions of this Agreement will not be deemed to be a continuing waiver or a waiver of any other default or of any other term or condition, but will apply solely to the instance to which such waiver is directed. The exercise of any right or remedy provided in this Agreement will be without prejudice to the right to exercise any other right or remedy provided by law or equity, except as expressly limited by this Agreement. Captions in this Agreement are for the convenience of the parties only and will not affect the interpretation or construction of this Agreement. In the event any provision of this Agreement is held to be invalid or unenforceable, such provision will be severed from the remainder of this Agreement, and such remainder will remain in force and effect. The parties agree to replace any such invalid provision with a valid provision that most closely approximates the intent and economic effect of the invalid provision. This Agreement constitutes the entire agreement between the parties relating to this subject matter and supersedes all prior and/or simultaneous representations, discussions, negotiations and agreements relating to the Pebble Mobile App(s), whether written or oral. --- # Pebble Developer Program Policies Source: https://developer.repebble.com/legal/program-policies/ The policies listed below play an important role in maintaining a positive experience for everyone using the Pebble Mobile App(s) and the related Products. Defined terms used here have the same meaning as in the Pebble Developer Distribution Agreement. Be sure to check back from time to time, as these policies may change. ## 1. Content Policies. Our content policies apply to any content your Product displays or links to, including any ads it shows to users and any user-generated content it hosts or links to. Further, our content policies apply to any content from your Developer Account, which is publicly displayed in the Pebble Mobile App(s), or any webpages or marketing materials for your Products. 1. **1.1.** **Sexually Explicit Material.** Products that contain or promote pornography are prohibited. This includes sexually explicit or erotic content, icons, titles, or descriptions. Pebble has a zero-tolerance policy against child sexual abuse imagery. If we become aware of content with child sexual abuse imagery, we will report it to the appropriate authorities and delete the accounts of those involved with the distribution. 2. **1.2.** **Violence and Bullying.** Depictions of gratuitous violence are not allowed. Products should not contain materials that threaten, harass or bully other users. 3. **1.3.** **Hate Speech.** Pebble does not allow content advocating against groups of people based on their race or ethnic origin, religion, disability, gender, age, veteran status or sexual orientation/gender identity. 4. **1.4.** **Impersonation or Deceptive Behavior.** Don't pretend to be someone else and don't represent that your Products are authorized by or produced by another company or organization if that is not the case. Products, apps or the ads they contain also must not mimic functionality or warnings from the operating system or other apps. Products must not contain false or misleading information in any content, title, icon, description or screenshots. Developers must not divert users or provide links to any other site that mimics or passes itself off as another app or service. Products and apps must not have names or icons that appear confusingly similar to existing products or to apps supplied with the Device. 5. **1.5.** **Intellectual Property.** Don't infringe on the intellectual property rights of others (including, without limitation, patent, trademark, trade secret, copyright and other proprietary rights) or encourage or induce infringement of intellectual property rights. We will respond to clear notices of alleged copyright infringement. For more information or to file a Digital Millennium Copyright Act request, please see our Terms of Service, which can be found at [https://rePebble.com/legal/terms\_of\_use](https://rePebble.com/legal/terms_of_use). 6. **1.6.** **Personal and Confidential Information.** We don't allow unauthorized publishing or disclosure of people's private and confidential information, such as credit card numbers, government identification numbers, driver's and other license numbers, non-public contacts or any other information that is not publicly accessible. 7. **1.7.** **Illegal Activities.** Keep it legal. Don't engage in unlawful activities on your Products, such as the sale of prescription drugs without a prescription. 8. **1.8.** **Gambling.** We don't allow content or services that facilitate online gambling, including, but not limited to, online casinos, sports betting and lotteries, or games of skill that offer prizes of cash or other value. 9. **1.9.** **Dangerous Products.** We don't allow Products that harm, interfere with the operation of, or access in an unauthorized manner, networks, servers or other infrastructure. Don't transmit or link to viruses, worms, defects, Trojan horses, malware or any other items that may introduce security vulnerabilities to or harm user Devices, apps or personal data. Products that collect information (such as the user's location or behavior) without the user's knowledge (spyware) are prohibited. Malicious scripts and password phishing scams are also prohibited on the Pebble Marketplace, as are apps that cause users to unknowingly download or install apps from sources outside of the Pebble Marketplace. A Product downloaded from the Pebble Mobile App(s) may not modify, replace or update its own binary code using any method other than the update mechanism of the Pebble Mobile App(s). ## 2. System Interference. 1. **2.1.** **Consent to Changes.** Products downloaded from the Pebble Mobile App(s) (or its components or derivative elements) must not make changes to the user's Device and/or Companion Device outside of the app without the user's knowledge and consent. This includes behavior, such as replacing or reordering the default presentation of apps, widgets or the settings on the Device. If an app makes such changes with the user's knowledge and consent, it must be clear to the user which app has made the change and the user must be able to reverse the change easily or uninstall the app altogether. 2. **2.2.** **Advertisements.** Products and their ads must not display advertisements through system level notifications on the user's Device, unless the notifications derive from an integral feature provided by the installed app (e.g., an airline app that notifies users of special deals or a game that notifies users of in-game promotions). 3. **2.3.** **Disabling of Third-Party Apps.** Products must not encourage, incentivize or mislead users into removing or disabling third-party apps, except as part of a security service provided by the Product. ## 3. Network Usage and Terms. Products must not create unpredictable network usage that has an adverse impact on a user's service charges or their carrier's network. Products also may not knowingly violate any third-party terms of service for allowed usage. ## 4. Spam and Placement in the Marketplace. 1. **4.1.** Do not post repetitive content. 2. **4.2.** Do not use irrelevant, misleading or excessive keywords in the Products' or apps' descriptions, titles or metadata. 3. **4.3.** Developers must not attempt to change the placement of any Products in the Pebble Mobile App(s) or manipulate any product ratings or reviews by unauthorized means, such as fraudulent installs, paid or fake reviews or ratings, or by offering incentives to rate Products. 4. **4.4.** Products that are created by an automated tool or wizard service must not be submitted to the Pebble Mobile App(s) by the operator of that service on behalf of other persons. 5. **4.5.** Do not post an app where the primary functionality is to: (a) drive affiliate traffic to a website; or (b) provide a view of a website not owned or administered by you (unless you have permission from the website owner/administrator to do so). 6. **4.6.** Do not send SMS, email or other messages on behalf of the user without providing the user with the ability to confirm content and intended recipient. ## 5. Promotion. Products published on the Pebble Mobile App(s) may not directly or indirectly engage in or benefit from the following behavior: (a) promotion via deceptive ads on websites, apps or other properties, including simulated system, service or app notifications or alerts; (b) promotion or install tactics that cause redirection to the Pebble Mobile App(s) or the download of the app without informed user action; or (c) unsolicited promotion via SMS services. It is your responsibility to ensure that no ad network or affiliate uses such methods to direct users to pages that make your Products available for download. ## 6. Ad Policy. The policy below covers all ads that are implemented in and bundled with Products and apps. This policy is important in maintaining a positive experience for everyone using the Pebble Platform. Be sure to check back from time to time, as these policies may change. 1. **6.1.** **Developer Terms Apply to the Entire User Experience of Your Products and Apps.** Please be aware that Pebble's Developer Distribution Agreement and these Pebble Developer Program Policies (collectively, the **"Developer Terms"** ) apply to each of your Products, as well as any ads or third-party libraries bundled or made available through such Products. Ads are considered part of your Products and apps for purposes of content review and compliance with the Developer Terms. Please take care to use advertising that does not violate the Developer Terms. Ads that are inconsistent with a Product's content rating also violate our Developer Terms. 2. **6.2.** **Ads Context.** Ads must not simulate or impersonate the user interface of any Products or notification and warning elements of an operating system. It must be clear to the user which Products or app each ad is associated with or implemented in. 3. **6.3.** **Ad Walls and Interstitial Ads.** Interstitial ads may only be displayed inside of the Products they came with. Forcing a user to click on ads or submit personal information for advertising purposes in order to fully use a Product is prohibited. A prominent and accessible target must be made available to users in any interstitial ad so they may dismiss the ad without penalty or inadvertent click-through. 4. **6.4.** **Interfering with Products and Third-Party Ads.** Ads associated with your Products must not interfere with other Products or their ads. ## 7. Policy Enforcement. In the event that your Product is removed from the Pebble Mobile App(s), you will receive an email notification to that effect. If you have any questions or concerns regarding a removal, you may contact us at [https://developer.repebble.com/contact/](https://developer.repebble.com/contact.md). Serious or repeated violations of the Developer Terms will result in the termination of your Developer Account. Repeated infringement of intellectual property rights, including copyright, will also result in termination of your Developer Account. --- # Pebble Legal Agreements Source: https://developer.repebble.com/legal/ This developer site, CloudPebble, the Developer Dashboard and the Pebble appstore are operated by Core Devices LLC. Your use of them is governed by the Core Devices [Terms of Service](https://repebble.com/terms) and [Privacy Policy](https://repebble.com/privacy). See [Terms of Use](https://developer.repebble.com/legal/terms-of-use.md). The [SDK License](https://developer.repebble.com/legal/sdk-license.md) page states the licenses the Pebble SDK, the `pebble` tool and this site are distributed under: - The SDK is built from PebbleOS and is distributed under the Apache License, Version 2.0. You can use, modify and redistribute it under that license. - "Pebble" is a trademark and there are restrictions on how you can use this word in the name and description of your app. See the [Trademark Usage Guidelines](https://developer.repebble.com/legal/pebble-trademark.md) for more details. - You are responsible for making sure that you have the rights to all the assets (code, images, fonts, etc.) that you include in your applications. - If you are collecting user, device or location data, or any personally identifiable information, you must get the user's consent first. To distribute your applications on the Pebble appstore, you will also need to read and agree to the [Pebble Developer Distribution Agreement](https://developer.repebble.com/legal/distribution.md) and the [Pebble Developer Program Policies](https://developer.repebble.com/legal/program-policies.md). - The applications you submit on the Pebble appstore do not go through a review process, but Core Devices can take down your app at any time if it does not comply with the agreement and the [Developer Program Policies](https://developer.repebble.com/legal/program-policies.md). - If your app has been taken down and you believe that it should not have been removed, [contact us](https://developer.repebble.com/contact.md). - In case of repeated takedowns, Core Devices can suspend and bar your developer account. - You may not distribute apps designed to interfere with, disrupt or damage the user's Pebble or mobile device. - You authorize Core Devices to use your submission (app binary, screenshots, etc.) to promote Pebble in marketing materials. --- # Pebble SDK License Source: https://developer.repebble.com/legal/sdk-license/ The Pebble SDK is built from the [PebbleOS](https://github.com/coredevices/pebbleos) source tree and is distributed under the [Apache License, Version 2.0](https://github.com/coredevices/pebbleos/blob/main/LICENSE). The license permits you to use, modify and redistribute the SDK, including as part of your own tools and apps, under the conditions set out in the license text. The `pebble` tool is distributed under the [MIT License](https://github.com/coredevices/pebble-tool/blob/main/LICENSE). The content of this developer site is licensed under the [Apache License, Version 2.0](https://github.com/coredevices/sdk-docs/blob/main/LICENSE) unless a page states otherwise. Apps you build with the SDK are yours. Distributing them through the Pebble appstore is covered by the [Pebble Developer Distribution Agreement](https://developer.repebble.com/legal/distribution.md) and the [Pebble Developer Program Policies](https://developer.repebble.com/legal/program-policies.md). Use of the Pebble name and logo is covered by the [Trademark Usage Guidelines](https://developer.repebble.com/legal/pebble-trademark.md). --- # Pebble Terms of Use Source: https://developer.repebble.com/legal/terms-of-use/ This developer site, CloudPebble, the Developer Dashboard and the Pebble appstore are operated by Core Devices LLC. Your use of these services is governed by the Core Devices [Terms of Service](https://repebble.com/terms) and [Privacy Policy](https://repebble.com/privacy). --- # Frequently Asked Questions Source: https://developer.repebble.com/faqs/ ## Pebble SDK What are the Aplite, Basalt, Chalk, Diorite, Emery and Flint platforms? These are the platform names used to describe different generations of watches. For example, Aplite refers to the original Pebble and Pebble Steel. You can find a full list, as well as information about the capabilities of each platform, on the [Hardware Information page](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) I cannot find the "Developer Mode" or "Developer Connection" toggle in the iOS or Android application Please make sure you have updated your mobile applications to at least 2.0. On iOS, you first need to enable Developer Mode in the global _Settings_ application on the phone. On Android, this can be enabled from the navigation drawer. `pebble` tool error: 'RuntimeError: Freetype library not found' Please install the freetype library on your computer: - Mac OS users can use the [homebrew](http://brew.sh/) packaging system and run `brew install freetype`. - Linux users should not have to do anything. How do I save the state between app invocations, for example, if I want to store user selections etc and persist it across app invocations? Use the [Persistent Storage API](https://developer.repebble.com/guides/events-and-services/persistent-storage.md), or [PebbleKit JS](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) `localStorage`. How do I calculate the total size of a dictionary? If you have a pointer to a `DictionaryIterator`, you can easily calculate the total size of this dictionary: ``` int size = (int)received-\>end - (int)received-\>dictionary; ``` Can I get realtime accelerometer data? Yes. See [this community blog post (external link)](http://ninedof.wordpress.com/2014/03/28/streaming-pebble-accelerometer-data/) for more information. ## App Communication App message error: 'The app rejected the update' This is likely caused by a mismatch between your watch application UUID and the UUID you registered for on the mobile application. Please make sure you use the same UUID on both ends. ## Memory Management How apps are loaded for execution? For example, are apps always in RAM, or loaded in RAM one at a time when the user starts that specific app? Apps (code and static variables) are loaded entirely in RAM when they are started. Your resources are only loaded in RAM when you call `resource_load()` or one of the function that calls it (like `fonts_load_custom_font()` or `gbitmap_create_with_resource_id()`). Your app, and all memory it has allocated, will be removed from RAM when it is terminated. What is the maximum code segment size? The data segment size? All segments share the same memory space. There is no limit on the segment sizes but the global limit of memory available to apps is currently around 24k bytes. Is there a separate Read-Only data segment vs a Read-Write data? No. For apps, the data segment is all Read-Write. Can one app invoke another app to get around app size limits? You can use the PebbleKit iOS or Android to launch any application on Pebble. ## Pebble SDK 1.x Migration In my old app `PBL_APP_INFO` I was setting the flags `APP_INFO_STANDARD_APP | APP_INFO_VISIBILITY_SHOWN_ON_COMMUNICATION`. How do I do that with the new `appinfo.json` file? You can add another flag to `package.json` under the `"watchapp"` block: ``` "watchapp": {"watchface": false"onlyShownOnCommunication": true} ``` ## Platforms and Devices Which platform name maps to which Pebble watch? Each platform name corresponds to a generation of hardware: - **Aplite** - original Pebble and Pebble Steel (black & white, rectangular) - **Basalt** - Pebble Time and Pebble Time Steel (64 colors, rectangular) - **Chalk** - Pebble Time Round (64 colors, round) - **Diorite** - Pebble 2 (black & white, rectangular, with heart rate) - **Flint** - Pebble 2 Duo (black & white, rectangular, re-released by Core Devices) - **Emery** - Pebble Time 2 (64 colors, rectangular, larger display) - **Gabbro** - Pebble Round 2 round (64 colors, round) The full capability matrix lives on the [Hardware Information page](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md). Use the corresponding `PBL_PLATFORM_*` and `PBL_BW`/`PBL_COLOR`/`PBL_ROUND`/`PBL_RECT` defines to write platform-aware code - see {% guide\_link best-practices/building-for-every-pebble "Building for Every Pebble" %}. Which platforms support which features? Color, shape, microphone, heart rate, and compass availability all vary by platform. The full feature matrix is in the table on the [Hardware Information page](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md). At compile time, use macros like `PBL_IF_COLOR_ELSE()`, `PBL_IF_ROUND_ELSE()`, and `PBL_IF_MICROPHONE_ELSE()` to branch on capabilities instead of hard-coding platform checks. The complete list is in {% guide\_link best-practices/building-for-every-pebble#available-defines-and-macros "Available Defines and Macros" %}. ## Developing for Pebble How do I install an app I'm developing onto a real watch? The default flow uses a cloud relay, so your computer and phone don't need to be on the same network. One-time setup: 1. Install the new Pebble mobile app from [repebble.com/app](https://repebble.com/app). 2. In the app, sign in with your Pebble account, then open **Devices → ⋯** and enable **Dev Connection**. 3. On your computer, run `pebble login` and sign in with the same Pebble account. Then build and install: ``` $ pebble build $ pebble install --cloudpebble ``` If you'd rather install over local Wi-Fi (the legacy route), enable the [Developer Connection](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) in the mobile app and use `pebble install --phone IP_ADDRESS` with the Server IP it shows. How do I install an app on the emulator instead of a physical watch? Pass `--emulator` with the platform you want to run: ``` $ pebble install --emulator basalt ``` Valid platforms are `aplite`, `basalt`, `chalk`, `diorite`, `emery`, `flint` and `gabbro`. The same flag works for `pebble logs`, `pebble screenshot`, and other commands that target a watch. Full reference: {% guide\_link tools-and-resources/pebble-tool "Pebble Tool" %}. How do I see logs from my app? Use `pebble logs --cloudpebble` (or `--emulator PLATFORM`, or `--phone IP_ADDRESS` for the legacy Wi-Fi route) to stream `APP_LOG` output. If you run `pebble install` with `-v` (or `-vvvv` for more detail) it will install and then immediately attach to the log stream. ## C Apps vs JavaScript Apps Should I write my app in C or JavaScript (Alloy)? Both run on the watch itself. Write in **C** when you need to support the full lineup of Pebble watches (Aplite through Gabbro), use the classic Pebble SDK UI primitives (`MenuLayer`, `ActionBarLayer`, etc.), or want the smallest possible memory footprint. Write in **JavaScript with [Alloy](https://developer.repebble.com/guides/alloy.md)** when you want modern JS (ES2025, modules, classes, async/await), the Piu (declarative) or Poco (procedural) UI frameworks from the Moddable SDK, and built-in `fetch`, WebSockets, and ECMA-419 device APIs. Alloy supports both watchfaces and watchapps. The trade-off today is platform reach: Alloy currently only targets **Emery (Pebble Time 2)** and **Gabbro (Pebble Round 2)**. If your app needs to run on Aplite, Basalt, Chalk, Diorite, or Flint, write it in C. Both C and Alloy apps can have a phone-side **PKJS** component for networking, location, and configuration - that's a separate JavaScript environment from your on-watch code. What should I know about Alloy's JavaScript runtime? Alloy uses Moddable's XS engine, which differs from browser/Node.js JavaScript in a few important ways: - **Platform support** : Emery and Gabbro only - no Aplite/Basalt/Chalk/Diorite/Flint support. - **Strict mode** is on by default for all code. - **Hardened JavaScript** : built-in objects (primordials) are frozen - you can't monkey-patch `Array.prototype` and friends. - **No `eval()`** or `Function()` constructor - keeps the engine footprint small. - **Two environments per app** : `src/embeddedjs/main.js` runs on the watch; `src/pkjs/index.js` runs on the phone for network and location. Scaffold a new project with `pebble new-project --alloy my-app`. Full details in {% guide\_link alloy/getting-started "Getting Started with Alloy" %}, and example apps live in the [Moddable Pebble Examples](https://github.com/Moddable-OpenSource/pebble-examples) repo. ## Memory and Resources How much memory does my app have? The app heap budget depends on the platform - Aplite is the tightest at around 24 KB, while Basalt, Chalk, Diorite, and Emery have substantially more. Use [`heap_bytes_free()`](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md#heap_bytes_free "heap\_bytes\_free") and [`heap_bytes_used()`](https://developer.repebble.com/docs/c/Foundation/Memory_Management.md#heap_bytes_used "heap\_bytes\_used") at runtime, and watch the `Free RAM` line in the build output to track your app's footprint. Tips for fitting into the smaller platforms are in {% guide\_link best-practices/building-for-every-pebble "Building for Every Pebble" %} and {% guide\_link migration/3x-aplite-migration "3.x Aplite Migration" %}. How should I manage images and other resources? Declare resources in the `resources` block of `package.json`, then load them at runtime with `gbitmap_create_with_resource_id()`, `fonts_load_custom_font()`, and similar calls. Resources are only loaded into RAM when you ask for them, so destroy them as soon as you're done. For images, prefer the `bitmap` resource type - the SDK will pick an efficient on-disk format per platform. You can also vary an asset per platform using `targetPlatforms` in the resource definition. See: - {% guide\_link app-resources/app-assets "App Assets" %} - {% guide\_link app-resources/images "Images" %} - {% guide\_link app-resources/platform-specific "Platform-Specific Resources" %} How do I create vector graphics (PDC) for animations and icons? Pebble Draw Command (PDC) files are a compact vector format the watch can render directly. The SDK ships an `svg2pdc.py` script for converting Inkscape or Illustrator SVGs - see {% guide\_link app-resources/converting-svg-to-pdc "Converting SVG to PDC" %}. For interactive editing, the community-maintained [PDC tool by Heiko Behrens](https://pdc-tool.heikobehrens.com/) lets you inspect, tweak, and preview PDC files in the browser, which is handy when you need to fine-tune a converted SVG. ## Emulator and Troubleshooting The emulator is stuck, crashing, or behaving oddly. How do I reset it? Run: ``` $ pebble wipe ``` This clears stored emulator state (installed apps, settings, persistent storage) without signing you out of your Pebble account. Add `--everything` to also wipe account data. After wiping, the next `pebble install --emulator PLATFORM` will start a fresh QEMU instance. `pebble build` or `pebble new-project` says no SDK is installed After installing the `pebble` tool you also need to fetch an SDK release. Run: ``` $ pebble sdk install latest ``` This downloads the most recent SDK and makes it the active one. You can list installed versions with `pebble sdk list` and switch between them with `pebble sdk activate VERSION`. The emulator won't start on Linux (missing SDL/glib/pixman errors) The Pebble QEMU build needs SDL2, glib, pixman, and zlib at runtime - they aren't pulled in by `pebble-tool` itself. Install them with your distro's package manager: - **Ubuntu/Debian** : `sudo apt install libsdl2-2.0-0 libglib2.0-0 libpixman-1-0 zlib1g` - **Fedora** : `sudo dnf install SDL2 glib2 pixman zlib` Then retry `pebble install --emulator PLATFORM`. The full setup steps are on the [SDK installation page](https://developer.repebble.com/sdk.md). ## Community Tools Is there an AI-assisted way to build watchfaces? Yes: Core Devices maintains a [Pebble Watchface Agent skill](https://github.com/coredevices/pebble-watchface-agent-skill) for Claude Code. It bundles the SDK knowledge an agent needs to scaffold, build, and iterate on a watchface project. Follow the README for installation and prompting tips. Where can I edit and preview PDC vector files visually? Heiko Behrens hosts a browser-based [PDC tool](https://pdc-tool.heikobehrens.com/) that loads, displays, and lets you tweak PDC files. It's particularly useful for inspecting the output of `svg2pdc.py` and adjusting paths without re-exporting from your vector editor. --- # Contact Developer Support Source: https://developer.repebble.com/contact/ Pebble is no longer providing direct 1-on-1 support for developers. We are now focusing our efforts upon our public support channels, which will directly benefit other developers who may be experiencing similar issues. If you have shipping, billing or other non-developer related questions, please contact [Pebble Support](mailto:info@repebble.com) instead. ### Developer Support In order to find a resolution to a development issue, you may want to try our [community forums](https://forum.repebble.com). - 1. Try searching to locate a resolution for your problem. - 2. If you were unable to locate a solution, create a new topic. Try to provide as much detail as possible, including logs, screenshots or code snippets. --- # Pebble Open Source Source: https://developer.repebble.com/open-source/ The Pebble firmware, SDK, mobile app and developer tools are open source. This page lists the repositories, along with SDKs written by the community. All Core Devices repositories are on GitHub at [https://github.com/coredevices/](https://github.com/coredevices/). ## Firmware and SDK ### [PebbleOS](https://github.com/coredevices/pebbleos/) The watch firmware. The Pebble SDK is built from this source tree. See the [PebbleOS section](https://developer.repebble.com/pebbleos.md) of this site to build it, run it in the emulator and contribute. ### [pebble-tool](https://github.com/coredevices/pebble-tool/) The `pebble` command line tool, which builds, installs and debugs apps and manages SDKs. ### [SDK documentation](https://github.com/coredevices/sdk-docs/) The source of this developer site. ### [Pebble examples](https://github.com/pebble-examples/) Example apps and watchfaces in C and PebbleKit JS, one repository per example. ### [Alloy examples](https://github.com/Moddable-OpenSource/pebble-examples/) Example apps and watchfaces written in JavaScript with Alloy. ### [Pebble watchface agent skill](https://github.com/coredevices/pebble-watchface-agent-skill/) A skill for AI coding agents that builds and tests Pebble watchfaces with the SDK and the emulator. ## Mobile App and Tools ### [Pebble mobile app](https://github.com/coredevices/mobileapp/) The Pebble mobile app for Android and iOS, including libpebble3, the Kotlin multiplatform library for talking to Pebble watches. ### [CloudPebble](https://github.com/coredevices/cloudpebble/) The web IDE for building Pebble apps in the browser. ### [QEMU](https://github.com/coredevices/qemu/) Fork of QEMU with Pebble machine types, used by the SDK emulator. ### [pypkjs](https://github.com/coredevices/pypkjs/) Python implementation of PebbleKit JS, used by the SDK emulator. ## Community SDKs ### [zig-pebble-sdk](https://github.com/vsergeev/zig-pebble-sdk/) Build Pebble watchapps and watchfaces in Zig, with access to the Pebble C API. ### [rust4pebble](https://github.com/jbellerb/rust4pebble/) Rust bindings for the Pebble SDK. --- # Home Source: https://developer.repebble.com/ # Welcome to the Pebble Developer Site Build apps and watchfaces for Pebble smartwatches. [Don't have a Pebble? Get one here.](https://repebble.com) ## Get Started Install the SDK locally or use CloudPebble, a browser-based IDE with no setup required. [Get the SDK](https://developer.repebble.com/sdk.md)[Go to CloudPebble](https://cloudpebble.repebble.com)[Build with an AI Agent](https://developer.repebble.com/guides/ai-agents.md) ## Tutorials Learn how to write your first Pebble app. Great for both new and experienced developers! [Build with JavaScript](https://developer.repebble.com/tutorials/alloy-watchface-tutorial/)[Build with C](https://developer.repebble.com/tutorials/watchface-tutorial/) ## PebbleOS Pebble watch firmware is now open source ([thanks Google!](https://ericmigi.com/blog/why-were-bringing-pebble-back/#todays-big-news-goog)). Build it, run it in the emulator, flash it to a watch and contribute changes. [Develop PebbleOS](https://developer.repebble.com/pebbleos.md) ## Index 01 Connect [Index 01](https://repebble.com/index) ring to your own services with webhooks and MCP servers, and extend it with plugins. [Build on Index 01](https://developer.repebble.com/index-01.md) --- # Pebble is back! Source: https://developer.repebble.com/weareback/ ### Pebble is back! In early 2025, Google open sourced PebbleOS (thank you Goog) and our new company Core Devices sprung up to begin making new Pebble watches. [Get a new Pebble](https://store.repebble.com)[Get the SDK](https://developer.repebble.com/sdk.md)[PebbleOS Github](https://github.com/coredevices/PebbleOS)[Read the blog](https://ericmigi.com) --- # Take PebbleOS To The World Source: https://developer.repebble.com/blog/2026/10/07/Take-PebbleOS-To-The-World/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 07 October 2026 - [Joshua Jun (JP)](https://developer.repebble.com/blog/authors/jplexer/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2026/10/07/Take-PebbleOS-To-The-World.md#comments) Pebble has always had a global community. From the very beginning, people all over the world have worn Pebbles on their wrists, built watchfaces for them, and kept them ticking long after anyone expected. But for a lot of those people, the watch on their wrist has never really spoken their language. Today we're changing that. We're launching [translate.repebble.com](https://translate.repebble.com), a new home for translating PebbleOS, and we'd love your help to take PebbleOS to the world. ## Why Translations Matter Your watch is one of the most personal devices you own. It's the first thing you glance at in the morning and the thing that taps you on the wrist when something important happens. Menus, settings, notifications and system messages should all feel natural to read at a glance, and that means reading them in the language you think in. Now that PebbleOS is open source, the community can make that happen. Instead of waiting for official language packs, anyone who speaks a language can help bring PebbleOS to the people who speak it too. ## Weblate, With A Pebble Twist translate.repebble.com is built on [Weblate](https://weblate.org), the open source translation platform used by many open source projects. If you've translated software before, you'll feel right at home: pick a language, work through the strings, and leave suggestions and comments for other translators. You don't need to clone a repository, set up a build environment or know anything about how the firmware works. Translating for a watch is a little different from translating for a phone or a desktop, though. Screens are tiny, every pixel counts, and the watch can only show characters its fonts contain. So we built **Peblate** , a Weblate extension that adds the Pebble-specific pieces right into the editor: - **Watch previews.** As you type, the editor shows your translation in a sample text box drawn by the real PebbleOS text renderer, compiled to run in your browser. You can see right away whether it fits and how it looks in Pebble's fonts. - **Font coverage checks.** Peblate checks your language against the fonts that will be used and tells you exactly which characters are missing, with examples, so there are no surprise empty boxes on the watch. - **Custom fonts.** Not every script is covered by Pebble's built-in fonts. For those languages you can upload a font along with its license. Peblate compiles it for every text style and lets you review it with the PebbleOS renderer before the language is created. - **Draft language packs.** Want to see your work on a real watch? Peblate can build a draft language pack from the translations saved so far, including ones still waiting for review, so you can test before anything is final. ## How To Get Involved 1. Head over to [translate.repebble.com](https://translate.repebble.com) and sign in with your Pebble Account. That's the same account you already use with the Pebble app, so there's no new password to remember. 2. Choose the language you'd like to work on. If yours isn't there yet, start it with the new-language wizard. It checks whether Pebble's built-in fonts cover your language and walks you through adding a font if they don't. 3. Start translating! Even a few strings help, and every bit counts. Each existing language already has a select few reviewers from the community. They check new translations before they make it into a language pack, which keeps each language consistent and high quality. So don't worry about getting everything perfect. Your suggestions will get a second pair of eyes. New languages are open to everyone. If your language isn't on the site yet, you don't need to ask permission. Start it and get translating! A few tips to keep in mind: - **Keep it short.** Pebble screens are small, and a translation that's much longer than the original may get cut off. Keep an eye on the preview. Short, clear wording beats a literal translation. - **Stay consistent.** If a term is already translated elsewhere, try to use the same wording so the interface feels cohesive. - **Ask when in doubt.** If you're not sure what a string means or where it appears, ask the community. Context makes for better translations. ## What's Next This is just the beginning. Draft packs are for testing for now. Next, we're working on full-screen watch previews and on publishing reviewed language packs so they reach everyone's watches. Translators who get started now will be the ones who make sure their language is ready on day one. ## Join Us PebbleOS is built by its community, and translations are one of the easiest and most rewarding ways to contribute. Whether you speak one language or five, you can help someone, somewhere, use their Pebble in their own language. If you have questions, feedback or want to coordinate with other translators for your language, come and say hello on the [Pebble forum](https://forum.repebble.com). We can't wait to see PebbleOS in your language. JP * * * --- # Pebble.js - Pebble Package Edition! Source: https://developer.repebble.com/blog/2016/12/22/pebblejs-package/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 22 December 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/12/22/pebblejs-package.md#comments) We're pleased to announce that [Pebble.js](https://pebble.github.io/pebblejs/) has now been [published](https://www.npmjs.com/package/pebblejs) as a [_Pebble Package_](https://developer.repebble.com/guides/pebble-packages.md). Pebble.js lets developers easily create Pebble applications using JavaScript by executing the JavaScript code within the mobile application on a user's phone, rather than on the watch. ![Pebble.js as a Pebble Package](https://developer.repebble.com/assets/images/blog/2016-12-22-pebble-js.jpg) Making Pebble.js a Pebble Package means Pebble.js projects can be converted to standard Pebble C projects. This gives benefits like the ability to easily utilize other Pebble Packages, such as [Clay for Pebble](https://www.npmjs.com/package/pebble-clay), or easily importing and exporting the project with [CloudPebble](https://cloudpebble.repebble.com/). The Pebble.js package is using the [`develop`](https://github.com/pebble/pebblejs/tree/develop) branch from the [Pebble.js repository](https://github.com/pebble/pebblejs) on Github, and can be updated independently from CloudPebble deployments. **It also supports the Diorite platform!**. ## Creating a New Project The initial steps vary if you're using CloudPebble or the Local SDK. Follow the appropriate steps below to create a new project. #### CloudPebble If you're using CloudPebble, follow these initial steps: 1. Create a new project: 2. Add the following dependency: 3. Add a new `main.c` file and an `index.js` file. Now continue to add the [default project files](#default-project-files). #### Local SDK If you're using the Local SDK, just create a new C project with Javascript support: ``` $ pebble new-project PROJECTNAME --javascript ``` Now continue to add the [default project files](#default-project-files). #### Default Project Files Copy and paste these default project files into your project, replacing any existing file contents: **your-main.c** ``` #include \#include "pebblejs/simply.h"int main(void) {Simply \*simply = simply\_create();app\_event\_loop();simply\_destroy(simply);} ``` **index.js** ``` require('pebblejs');var UI = require('pebblejs/ui');var card = new UI.Card({title: 'Hello World',body: 'This is your first Pebble app!',scrollable: true});card.show(); ``` At this point you should be able to compile and run your new project. ## Migrating an Existing Project Unfortunately there isn't an automated way to migrate your existing Pebble.js project, but the steps are fairly straightforward. 1. Create a new project, following the [steps above](#creating-a-new-project). 2. Change the project settings to match your old project settings, including the UUID. 3. Copy your project resources (images, fonts etc.), and source files into the new project. 4. Compile and enjoy your new C project with Pebble.js support. > Note: `index.js` is a direct replacement for `app.js`, which may be your old Javascript file. ## Next Steps? Want to add Clay support to your project? It's now easy by following the standard Clay [Getting Started](https://github.com/pebble/clay#clay) instructions! If you have any questions or problems, post the details on the [developer forum](https://forums.pebble.com/t/pebble-js-pebble-package-edition/27315) or [Discord](http://discord.gg/aRUAYFN). Happy Holidays! Jon Barlow + Team #PEBBLExFITBIT * * * --- # Announcing Pebble SDK 4.3 Source: https://developer.repebble.com/blog/2016/11/23/Announcing-Pebble-SDK-4.3/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 23 November 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/11/23/Announcing-Pebble-SDK-4.3.md#comments) Things have been a bit 'quiet' recently, but we're back with another fresh Pebble SDK! In this release we've included one of the most frequently requested APIs, exposed the raw HRM sensor value, released PebbleKit 4.0, plus added an exciting new BLE HRM mode. ## It's. Oh. So quiet. Ssshhhhhh. We've added an often requested method for developers to detect if _Quiet Time_ is enabled. To say that we had a lot of requests for this would be an understatement. _Quiet Time_ can be enabled manually, via calendar events, or via scheduled times, so we've made a simple method for querying whether it's currently active. All you need to do is check each minute if it's active. This is easily done within the tick event, as follows: ``` static void handle\_tick(struct tm \*tick\_time, TimeUnits units\_changed) {if (units\_changed & MINUTE\_UNIT) {if (quiet\_time\_is\_active()) {// It's nice and quiet} else {// Starts another big riot}}} ``` You may also peek this value, for example, to prevent your application from vibrating during _Quiet Time_: ``` static void do\_vibration() {if (!quiet\_time\_is\_active()) {vibes\_short\_pulse();}} ``` ## Raw HRM BPM The Pebble [Health API](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md "HealthService") now exposes the raw BPM value from the Heart Rate Monitor sensor. This raw value is not filtered and is useful for applications which need to display the real-time sensor value, similar to the Pebble Workout app. For example, in order to peek the current real-time HRM sensor, you would do the following: ``` HealthServiceAccessibilityMask hr = health\_service\_metric\_accessible(HealthMetricHeartRateRawBPM, time(NULL), time(NULL));if (hr & HealthServiceAccessibilityMaskAvailable) {HealthValue val = health\_service\_peek\_current\_value(HealthMetricHeartRateRawBPM);if(val \> 0) {// Display raw HRM valuestatic char s\_hrm\_buffer[8];snprintf(s\_hrm\_buffer, sizeof(s\_hrm\_buffer), "%lu BPM", (uint32\_t)val);text\_layer\_set\_text(s\_hrm\_layer, s\_hrm\_buffer);}} ``` Find out more in the [_Heart Rate Monitor API guide_](https://developer.repebble.com/guides/events-and-services/hrm.md). ## PebbleKit 4.0 PebbleKit for [iOS](https://github.com/pebble/pebble-ios-sdk/) and [Android](https://github.com/pebble/pebble-android-sdk/) facilitates communication between Pebble watchapps and 3rd party companion phone apps. Version 4.0 introduces a number of new features and bug fixes, including: - Support for Pebble 2 _(required for iOS only)_ - Removal of Bluetooth Classic (iOS) - Sports API - Support 3rd party HRM - Sports API - Custom data field and label - Sports API - Helper object to simplify usage, and minimize updates via Bluetooth For further information about the specific platform changes, view the full [iOS](https://github.com/pebble/pebble-ios-sdk/), or [Android](https://github.com/pebble/pebble-android-sdk/) changelogs. ## BLE HRM Mode Ever wanted to use your _Pebble 2 HRM_ as a dedicated BLE HRM device with your favourite mobile fitness app? Well now you can! Firmware 4.3 now implements the standard [Bluetooth Heart Rate Service profile](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=239866). In order to enable this profile, users must enable 'Pebble Health', then enable 'Heart Rate Monitoring' within the `Pebble Health` settings within the Pebble mobile app. Developers who are looking to integrate directly with this profile should be aware of the following: The Heart Rate Service UUID will be present in the advertising payload of Pebble, but you must open the Bluetooth settings on the Pebble to make it advertise and be discoverable over Bluetooth. Because it's highly likely that the Pebble is already connected to the phone, it will not be advertising. Therefore, we recommend that your mobile app also enumerates already connected devices, to see if any of them support the Heart Rate Service. This is in addition to scanning for advertisements to find new devices that support HRS. By enumerating connected devices, you improve the user experience: users will not have to go into the Bluetooth settings menu on Pebble if their Pebble is already connected to the phone. The first time an application subscribes to the Heart Rate Measurement characteristic, a UI prompt will appear on the Pebble, asking the user to allow sharing of their heart rate data. This permission is stored for the duration of the Bluetooth connection. When HR data sharing is active, the HR sensor will run continuously at ~1Hz sample rate. This means there is a significant battery impact when using this feature for an extended period of time. When all applications have unsubscribed from the Heart Rate Measurement characteristic, the HR sensor automatically returns to its default state. Mobile apps should unsubscribe from the Heart Rate Measurement characteristic as soon as the data is no longer required. For example, a workout app should unsubscribe when the workout has finished and/or the application is exited. If the Heart Rate Service is used continuously for a prolonged period of time (currently 4hrs), a notification is presented on the watch to remind the user that the HR data is still being shared. The user can stop sharing HRM data from _Settings \> Bluetooth \> Device_. If the user chooses to stop sharing, the Bluetooth connection is briefly disconnected and reconnected to forcefully remove all subscribers. Unfortunately the Bluetooth GATT specification does not provide a better mechanism for a service to unsubscribe subscribers, only subscribers can unsubscribe themselves. Service Characteristics Notes: - 'Sensor Contact' field is used. - 'Body Sensor' characteristic is used. The value is constant though (It will read "Wrist" / 0x02) - 'RR Interval' is currently NOT used. - 'Energy Expended' field is currently NOT used. - 'Heart Rate Control Point' characteristic is currently NOT used. ## What's Next Check out the [release notes](https://developer.repebble.com/sdk/changelogs/4.3.md) for a full list of changes and fixes that were included in SDK 4.3. Let us know on [Twitter](https://twitter.com/pebble/) if you built something cool using the new APIs! We'd love to hear about your experiences with the SDK. Happy Hacking! Team Pebble * * * --- # 4.2-beta4 SDK - Emery Edition! Source: https://developer.repebble.com/blog/2016/10/11/Emery-SDK-Beta/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 11 October 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/10/11/Emery-SDK-Beta.md#comments) We're incredibly excited to announce the first public beta of the Pebble SDK 4.2. This is the first time that developers can experience the new 'Emery' platform which is specifically created for the upcoming [Pebble Time 2](https://www.pebble.com/buy-pebble-time-2-smartwatch). ![Pebble Time 2](https://developer.repebble.com/assets/images/blog/2016-10-11-intro.jpg) ## All About Those Pixels The new display on the _Pebble Time 2_ is almost 53% physically larger, and the [Pixels per inch](https://en.wikipedia.org/wiki/Pixel_density) (PPI) has been increased from 182 PPI to 202 PPI. This is a massive 200x228 pixels, compared to 144x168 on the _Pebble Time Steel_, that's an 88% increase in the total amount of pixels!. Take a look at our [_Hardware guide_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) for further information about the specifications of the Pebble Time 2. Watchfaces and watchapps that have not been updated for the Emery platform will run in _Bezel Mode_. This means they will appear centered on screen at their original resolution (144x168), but due to the change in PPI, they appear slightly smaller. ![Pebble Time 2 Bezel](https://developer.repebble.com/assets/images/blog/2016-10-11-bezel.png) Left: Pebble Time Steel, Middle: Pebble Time 2 - Bezel Mode, Right: Optimized for Pebble Time 2. [Concentricity Watchface](https://github.com/pebble-examples/concentricity) The increased number of pixels would immediately make you think that you can just add more elements to your watchface design, but due to the increase in PPI, existing elements and fonts may feel slightly smaller than expected when viewed on an actual device. The image below simulates how the PPI difference makes a bezel mode application feel smaller on the Pebble Time 2. ![Pebble Time 2 PPI](https://developer.repebble.com/assets/images/blog/2016-10-11-dpi-comparison.png) Left: Pebble Time Steel, Right: Pebble Time 2 - Bezel Mode. We've now seen that the increased amount of pixels doesn't necessarily equate to bigger text on our devices, due to the change in PPI, that's why we've created the new [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) API. ## Introducing the ContentSize API All existing Pebble smartwatches provide the ability for users to change the size of text for notifications and some system UI components using _Settings \> Notifications \> Text Size_. The new [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) API now exposes this setting to developers, in order for them to adapt their application design and layout based on this user preference. In the previous section, we saw how the PPI increase on Pebble Time 2 affected the relative size of a watchface. In the image below you can see precisely how text sizes are affected by the PPI change. ![Pebble Time 2 ContentSize](https://developer.repebble.com/assets/images/blog/2016-10-11-contentsize.png) Yellow: Pebble Time Steel, Green: Pebble Time 2. To negate the effects of this reduced font size, the Emery platform uses larger fonts by default. Developers can use the [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) API to match this behaviour within their own applications. The following table explains how the text size setting affects the content size on each platform: | Platform | Text Size: Small | Text Size: Medium | Text Size: Large | | --- | --- | --- | --- | | Aplite, Basalt, Chalk, Diorite | ContentSize: Small | ContentSize: Medium | ContentSize: Large | | Emery | ContentSize: Medium | ContentSize: Large | ContentSize: Extra Large | Developers should aim to implement designs which adapt to the capabilities of the device, but also the accessibility requirements of the user. The [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) API satisfies both of these goals. For more information, read the [_ContentSize guide_](https://developer.repebble.com/guides/user-interfaces/content-size.md). ## New for Rocky.js With the release of SDK 4.2, we're extremely proud to announce that [Rocky.js](https://developer.repebble.com/docs/rockyjs/) watchfaces can be published into the Pebble appstore! As usual, don't publish apps with the beta SDK into the appstore, please wait for the final release. In addition to finalizing the memory contract for Rocky.js apps, we've also added a few new APIs to work with: ### Memory Pressure The `memorypressure` event has been added to allow developers to handle the situations which occur when the amount of memory available to their application has changed. Initially we've only implemented the `high` memory pressure level, which occurs right before your application is about to be terminated, due to a lack of memory. If you can free sufficient memory within the callback for this event, your application will continue to run. Please refer to the Rocky.js [documentation](https://developer.repebble.com/docs/rockyjs/rocky/#RockyMemoryPressureCallback) and the memory pressure [example application](https://github.com/pebble-examples/rocky-memorypressure). ### UserPreferences The first watchface setting exposed in the new `UserPreferences` object is `contentSize`. This exposes the new [ContentSize](https://developer.repebble.com/docs/c/User_Interface/Preferences.md#preferred_content_size) API to Rocky.js watchface developers. Find out more in the Rocky.js [UserPreferences documentation](https://developer.repebble.com/docs/rockyjs/rocky/#userPreferences). ## What's Next Check out the [release notes](https://developer.repebble.com/sdk/changelogs/4.2-beta4.md) for a full list of changes and fixes that were included in SDK 4.2-beta4. Let us know on [Twitter](https://twitter.com/pebble/) if you built something cool using the new APIs! We'd love to hear about your experiences with the SDK. Happy Hacking! Team Pebble * * * --- # Ready for Pebble 2 Source: https://developer.repebble.com/blog/2016/10/04/ready-for-pebble-2/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 04 October 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/10/04/ready-for-pebble-2.md#comments) The latest 4.1 releases of the firmware, mobile apps and SDK bring support for the new Pebble 2 smartwatches, which have started appearing on the wrists of developers and reviewers around the globe. ![Pebble 2 in the Wild](https://developer.repebble.com/assets/images/blog/2016-09-27-silk-in-the-wild.jpg) Pebble 2 smartwatches are on their way to Kickstarter backers, and firmware 4.1 will be ready and waiting for them. ## Heart Rate Monitor API The HRM APIs allow allow developers to query the user's current and historical heart rates. We've provided a simple to use API which caters for the most common use-cases. ![HRM](https://developer.repebble.com/assets/images/blog/2016-09-27-heart-beat.gif) Find out more in the [_Heart Rate Monitor API guide_](https://developer.repebble.com/guides/events-and-services/hrm.md). ## AppGlance REST API The AppGlance REST API enables developers to programmatically set the icon and subtitle that appears alongside their app in the launcher. Developers can push slices to the their app's glance using their own backend servers by sending HTTPS requests to the timeline endpoint. ![HRM](https://developer.repebble.com/assets/images/blog/2016-05-24-kickstarter-3/launcher.gif) Find out more about [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") in the SDK documentation, or read the [AppGlance REST API guide](https://developer.repebble.com/guides/user-interfaces/appglance-rest/). ## What's Next Check out the [release notes](https://developer.repebble.com/sdk/changelogs/4.1.md) for a full list of changes and fixes that were included in SDK 4.1. Let us know on [Twitter](https://twitter.com/pebble/) if you built something cool using the new APIs! We'd love to hear about your experiences with the SDK. Happy Hacking! Team Pebble * * * --- # Timeline - The Future of the Past! Source: https://developer.repebble.com/blog/2016/09/06/timeline-the-future-of-the-past/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 06 September 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/09/06/timeline-the-future-of-the-past.md#comments) If you’ve been living under a rock, or have just been sunning yourself on the beach for the past week, you might have missed the [launch of Pebble OS 4.0](https://developer.repebble.com/blog/2016/08/30/announcing-pebble-sdk4.md). This new version introduced some fantastic new features, such as the [updated health app](https://blog.getpebble.com/2016/08/30/fw4-0/), [_timeline quick view_](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md), [_app glances_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md), [Rocky.js](https://developer.repebble.com/blog/2016/08/15/introducing-rockyjs-watchfaces.md) and a new [system launcher](https://developer.repebble.com/blog/2016/08/19/prime-time-is-approaching-for-os-4.0.md#menu-icon-in-the-launcher). ### The Past and the Furious However, there was one change which was met with mixed feedback from both users and developers alike, the removal of timeline past. Previously accessible via the UP button, timeline past was removed as part of the new 4.0 user experience (UX). In 4.0 we introduced new APIs to give developers more options to improve their application’s UX and potentially shift away from using the past for interactions Unfortunately, this change prevented users from accessing any timeline pin which wasn’t in the present or future, negatively affecting a number of apps and their use cases for the timeline. We carefully listened to feedback and suggestions from our developer community via the [forums](https://forums.pebble.com), [Reddit](https://www.reddit.com/r/pebble), [Twitter](https://twitter.com/pebbledev) and Discord, and we are happy to announce that timeline past has returned in the v4.0.1 update. Users who need to access the timeline past can now assign it to one of their quick launch buttons. ![Quick Launch](https://developer.repebble.com/assets/images/blog/2016-09-06-quick-launch.gif) And so, with the reintroduction of timeline past, balanced was restored, and that was the end of the story. Or was it? ### Back to the Future! If you’re the developer of an application which relies upon timeline past, you will probably want to inform your users about how they can access timeline past, as it will not be enabled by default. Fortunately, there are multiple ways in which you can do this easily. #### 1. App Store Description Use the app store description of your app to explain that your application utilizes timeline past and that users will need to assign it to quick launch. For example: > This application utilizes pins in the timeline past. On your Pebble, go to ‘Settings’, ‘Quick Launch’, ‘Up Button’, then select ‘Timeline Past’. You can then access timeline past by long pressing UP. #### 2. Display a Splash Screen Add a splash screen to your application which only runs once, and display a message informing users how to enable timeline past. You could use the ‘[about-window](https://www.npmjs.com/package/@smallstoneapps/about-window)’ Pebble package for a really quick and easy solution. ![About Window](https://developer.repebble.com/assets/images/blog/2016-09-06-about-window.png) #### 3. Display a One-Time Notification Display a [system notification](https://developer.repebble.com/guides/communication/using-pebblekit-js.md#showing-a-notification) which only fires once, and display a message informing users how to enable timeline past. ![System Notification](https://developer.repebble.com/assets/images/blog/2016-09-06-system-notification.png) #### 4. Display a Timeline Notification Display a [timeline notification](https://developer.repebble.com/guides/pebble-timeline.md), and display a message informing users how to enable timeline past. ![Timeline Notification](https://developer.repebble.com/assets/images/blog/2016-09-06-timeline-notification.png) ### The Future of the Past For now, developers can continue to utilize timeline past, but over time we would like to provide a more diverse set of functionality that allows developers to surface information to their users. For example, some use cases of timeline past may be more appropriate as using an app glance, or timeline quick view instead. ### We’re Listening! Your feedback is incredibly important to us, it’s how we keep making awesome things. We love to receive your product and feature [suggestions](http://pbl.io/ideas) too. We’re particularly interested to hear about your use cases and ideas for timeline as we travel further into the future! Let us know via [the forums](https://forums.pebble.com), [Twitter](https://twitter.com/pebbledev) and Discord! * * * --- # Announcing Pebble SDK 4.0 Source: https://developer.repebble.com/blog/2016/08/30/announcing-pebble-sdk4/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 30 August 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/08/30/announcing-pebble-sdk4.md#comments) Today we have the pleasure of announcing the release of Pebble SDK 4.0. We have published an updated version of the Pebble Tool, the SDK itself, and we've deployed 4.0 onto [CloudPebble](https://cloudpebble.repebble.com/). It's time to get busy! ## What's New Pebble OS 4.0 has been released and users with Pebble Time, Pebble Time Steel and Pebble Time Round will be receiving the update today. We covered the new features in detail [very recently](https://developer.repebble.com/blog/2016/08/19/prime-time-is-approaching-for-os-4.0.md), but let's have a quick reminder of the new APIs and functionality that's included. ### Rocky.js Javascript on the freakin' watch! Although still in beta, [Rocky.js](https://developer.repebble.com/docs/rockyjs/) lets you start developing watchfaces in JavaScript using standard Web APIs, which you can then run directly on your watch. It's an embedded JavaScript revolution! Read the updated [Rocky.js blog post](https://developer.repebble.com/blog/2016/08/15/introducing-rockyjs-watchfaces.md) to get started. ### Timeline Quick View Timeline Quick View displays upcoming information from your timeline on your watchface. We introduced the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API to allow developers to detect if the screen is being obstructed by a system modal, such as Timeline Quick View. Developers can use this new API to adapt their watchface layout according to the available screen real estate. Read the [_UnobstructedArea guide_](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md) to get started. ### AppGlances With the new `AppGlance` API, developers can dynamically change the icon and subtitle of their watchapp within the system launcher, at runtime. This allows developers to provide meaningful feedback to users without the need for their application to be launched. Read the [_AppGlance C guide_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) and the [_AppGlance PebbleKit JS guide_](https://developer.repebble.com/guides/user-interfaces/appglance-pebblekit-js.md) to get started. ### Diorite Platform The new Pebble 2 devices use apps built for the Diorite platform, so you'll need SDK 4.0 to develop applications which target those devices. Take a look at the [_Hardware Information guide_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) to find out about the capabilities of the Pebble 2. ### One Click Action application The One Click Action application pattern promotes a type of watchapp which serves a single purpose. It launches, performs an action, and then terminates. This pattern utilizes the new `AppGlance` and [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") APIs. Take a look at the [_One Click Actions guide_](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) to get started. ## Dude, Where's my HRM API? We had planned on shipping the Heart Rate API with 4.0, but it's been pushed back into 4.1 so that we can add even more awesomeness. Pebble 2 devices will begin to appear on wrists after firmware 4.1 ships, so you'll still have time to begin implementing HRM data into your watchapps and watchfaces. We will announce details of the HRM API as soon as it's available. ## What's Next Please remember that we will promote watchfaces and watchapps that make use of these new 4.0 APIs if you submit them to the appstore **from August 31st onwards**. Let us know on [Twitter](https://twitter.com/pebble/) if you build something cool using the new APIs! We'd love to hear about your experiences with the SDK. Happy Hacking! Team Pebble * * * --- # Prime Time is Approaching for OS 4.0 Source: https://developer.repebble.com/blog/2016/08/19/prime-time-is-approaching-for-os-4.0/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 19 August 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/08/19/prime-time-is-approaching-for-os-4.0.md#comments) Pebble developers of the world unite! Pebble OS 4.0 is just around the corner and it's now time to put those finishing touches on your projects, and prepare them for publishing! Pebble Time owners will be receiving OS 4.0 before Kickstarter backers receive their Pebble 2 watches, so we're recommending that developers publish their 4.0 watchapps and watchfaces into the appstore **from August 31st onwards**. We'll be promoting new and updated apps which utilize the new SDK 4.0 APIs and features by creating a brand new category in the appstore called 'Optimized for 4.0'. This is your time to shine! If you haven't even begun to prepare for 4.0, there are some really quick wins you can use to your advantage. ## Menu Icon in the Launcher The new launcher in 4.0 allows developers to provide a custom icon for their watchapps and watchfaces. ![Launcher Icon](https://developer.repebble.com/assets/images/blog/2016-08-19-pikachu-icon.png) ![Launcher](https://developer.repebble.com/assets/images/blog/2016-08-19-pikachu-launcher.png) > If your `png` file is color, we will use the luminance of the image to add some subtle gray when rendering it in the launcher, rather than just black and white. Transparency will be preserved. You should add a 25x25 `png` to the `resources.media` section of the `package.json` file, and set `"menuIcon": true`. Please note that icons that are larger will be ignored and your app will have the default icon instead. ``` "resources": {"media": [{"menuIcon": true,"type": "png","name": "IMAGE\_MENU\_ICON","file": "images/icon.png"}]} ``` ## Timeline Quick View This new feature really brings your timeline into the present, with your next appointment appearing as a modal notification over the active watchface. We introduced the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API in 4.0 to allow developers to detect if the screen is being obstructed by a system modal. Below you can see two examples of the Simplicity watchface. The Pebble on the left is not using the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API, and the Pebble on the right is. ![Simplicity](https://developer.repebble.com/assets/images/blog/2016-08-19-simplicity-std.png) Watchface not updated ![Simplicity](https://developer.repebble.com/assets/images/blog/2016-08-19-simplicity-qv.png) Respects new 4.0 unobstructed area You can detect changes to the available screen real-estate and then move, scale, or hide their layers to achieve an optimal layout while the screen is partially obscured. This provides a fantastic experience to your users. There's [_a dedicated guide_](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md) which goes into more detail about the [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md "UnobstructedArea") API, it contains examples and links to sample implementations. ## AppGlances While your static app icon and app title from the `package.json` are being used as the default to present your app to the user, _AppGlances_ allow developers to control this content at runtime and to provide meaningful feedback to users directly from the new launcher. The API exposes the ability to dynamically change the `icon` and `subtitle_template_string` text of your application in the launcher. ![Updated Launcher](https://developer.repebble.com/assets/images/blog/2016-05-24-kickstarter-3/launcher.gif) Preview version of 4.0 launcher Utilizing the [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") API doesn't need to be difficult. We've provided guides, examples and sample applications for using the [_AppGlance C API_](https://developer.repebble.com/guides/user-interfaces/appglance-c.md) and the [_AppGlance PebbleKit JS API_](https://developer.repebble.com/guides/user-interfaces/appglance-pebblekit-js.md). ## The Diorite Platform The Diorite platform was created for the new Pebble 2 devices. Its display is rectangular, 144x168 pixels, with 2 colors (black and white). It has a microphone and heart rate monitor, but it doesn't have a compass. If your app works with Aplite it will already work with Diorite, but there are some important considerations to note: - If you chose to limit your app to some platforms, you need to add `"diorite"` to your `targetPlatforms` in the `package.json`. - Check for the appropriate usage of [_compiler directives_](https://developer.repebble.com/guides/best-practices/building-for-every-pebble.md#available-defines-and-macros). In general, it's better to use capability and feature detection, rather than platform detection. For example, when dealing with resources, use the suffix `~bw` instead of `~aplite` so they are picked on all black and white platforms. ## What's Next We're really excited for the release of Pebble OS 4.0 and the new features it brings. It's now time for you to take advantage of the new APIs and enhance your existing projects, or even create entirely new ones! Why not build something like the [_One Click Action_](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) application, which utilizes the new [`App Glance`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md "App Glance") and [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md#AppExitReason "AppExitReason") APIs. Please remember that we will promote watchfaces and watchapps that make use of these new 4.0 APIs if you submit them to the appstore **from August 31st onwards**. Let us know on [Twitter](https://twitter.com/pebbledev) if you build something cool using the new APIs! We'd love to hear about your experiences with the SDK. Happy Hacking! Team Pebble * * * --- # Introducing Rocky.js Watchfaces! Source: https://developer.repebble.com/blog/2016/08/15/introducing-rockyjs-watchfaces/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 15 August 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/08/15/introducing-rockyjs-watchfaces.md#comments) We're incredibly excited and proud to announce the first public beta version of Rocky.js watchfaces for Pebble. Rocky.js is ECMAScript 5.1 JavaScript running natively on Pebble smartwatches, thanks to [our collaboration](https://github.com/Samsung/jerryscript/wiki/JerryScriptWorkshopApril2016) with [JerryScript](https://github.com/pebble/jerryscript). This is an incredible feat of engineering! ## It's JavaScript on the freakin' watch! ``` var rocky = require('rocky');rocky.on('minutechange', function(event) {rocky.requestDraw();});rocky.on('draw', function(event) {var ctx = event.context;ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);ctx.fillStyle = 'white';ctx.textAlign = 'center';var w = ctx.canvas.unobstructedWidth;var h = ctx.canvas.unobstructedHeight;ctx.fillText('JavaScript\non the watch!', w / 2, h / 2);}); ``` ![Rocky](https://developer.repebble.com/assets/images/blog/2016-08-16-jotw.png) Right now we're giving you early access to this game-changing environment to create watchfaces entirely in JavaScript. Feel free to share your code, to install it on the emulator and on your watch (with the upcoming firmware 4.0), but please don't upload apps to the appstore as they will stop working in a future release. **Appstore Publishing** Rocky.js JavaScript watchfaces must not be published into the appstore at this time. We can't wait to hear your feedback and see what amazing watchfaces you create! Enough hype, let's dive into the eye of the tiger! ## Rocky.js Projects Things are a little different in the JavaScript world, so let's take a closer look at the structure of a Rocky.js project: ``` package.json src/rocky/index.js src/pkjs/index.js common/*.js ``` #### package.json The format of the `package.json` file remains roughly the same as existing Pebble projects, with some minor differences: - `"projectType": "rocky"`. - Aplite `targetPlatform` is not supported. - `resources` are not currently supported, but will consume space in your PBW if specified. - `messageKeys` are not permitted. #### src/rocky/index.js This is now the main entry point into our application on the watch. This is where our Rocky.js JavaScript code resides. All code within this file will be executed on the smartwatch. Additional scripts may be placed in this folder, see [below](#additional-scripts). #### src/pkjs/index.js This file contains our [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) (pkjs) JavaScript code. This code will execute on the mobile device connected to the smartwatch. Additional scripts may be placed in this folder, see [below](#additional-scripts). For Rocky.js projects only, we've added a new simplified communication channel [`postMessage()`](https://developer.repebble.com/docs/rockyjs/rocky/#postMessage) and [`on('message', ...)`](https://developer.repebble.com/docs/rockyjs/rocky/#on) which allows you to send and receive JavaScript JSON objects between the phone and smartwatch. #### Additional Scripts Both the `rocky` and `pkjs` folders support the use of multiple .js files, which helps to keep your code clean and modular. Use the [CommonJS Module](http://www.commonjs.org/specs/modules/1.0/) format for your additional scripts, then `require()` them in your `index.js`. ``` // file: src/rocky/additional.jsfunction test() {console.log('Additional File');}module.exports.test = test; ``` ``` // file: src/rocky/index.jsvar additional = require('./additional');additional.test(); ``` #### common/\*.js If you need to share code between `rocky` and `pkjs`, you can create a `common` folder, then add your JavaScript files. ``` // file: src/common/shared.jsfunction test() {console.log('Hello from shared code');}module.exports.test = test; ``` ``` // file: src/rocky/index.jsvar shared = require('../common/shared');shared.test(); ``` ``` // file: src/pkjs/index.jsvar shared = require('../common/shared');shared.test(); ``` ## Available APIs In this initial release of Rocky.js, we have focused on the ability to create watchfaces only. We will be adding more and more APIs as time progresses, and we're determined for JavaScript to have feature parity with the rest of the Pebble developer ecosystem. We've developed our API in-line with standard Web APIs, which may appear strange to existing Pebble developers, but we're confident that this will facilitate code re-use and provide a better experience overall. ### System Events We've provided a series of events which every watchface will likely require, and each of these events allow you to provide a callback method which is emitted when the event occurs. Existing Pebble developers will be familiar with the tick style events, including: [`secondchange`](https://developer.repebble.com/docs/rockyjs/rocky/#on), [`minutechange`](https://developer.repebble.com/docs/rockyjs/rocky/#on), [`hourchange`](https://developer.repebble.com/docs/rockyjs/rocky/#on) and [`daychange`](https://developer.repebble.com/docs/rockyjs/rocky/#on). By using these events, instead of [`setInterval`](https://developer.repebble.com/docs/rockyjs), we're automatically kept in sync with the wall clock time. We also have a [`message`](https://developer.repebble.com/docs/rockyjs/rocky/#on) event for receiving JavaScript JSON objects from the `pkjs` component, and a [`draw`](https://developer.repebble.com/docs/rockyjs/rocky/#on) event which you'll use to control the screen updates. ``` rocky.on('minutechange', function(event) {// Request the screen to be redrawn on next passrocky.requestDraw();}); ``` ### Drawing Canvas The canvas is a 2D rendering context and represents the display of the Pebble smartwatch. We use the canvas context for drawing text and shapes. We're aiming to support standard Web API methods and properties where possible, so the canvas has been made available as a [CanvasRenderingContext2D](https://developer.repebble.com/docs/rockyjs/CanvasRenderingContext2D/). > Please note that the canvas isn't fully implemented yet, so certain methods and properties are not available yet. We're still working on this, so expect more in future updates! ``` rocky.on('draw', function(event) {var ctx = event.context;ctx.fillStyle = 'red';ctx.textAlign = 'center';ctx.font = '14px Gothic';var w = ctx.canvas.unobstructedWidth;var h = ctx.canvas.unobstructedHeight;ctx.fillText('Rocky.js Rocks!', w / 2, h / 2);}); ``` ## Limitations We are still in the early beta phase and there are some limitations and restrictions that you need to be aware of: - Don't publish Rocky.js watchfaces to the Pebble appstore yet, they will stop working. - Rocky.js watchfaces only run on the 4.0 emulators/firmware. The 4.0 firmware will be available soon for Basalt, Chalk and Diorite. - No support for custom fonts, images and other resources, yet. - No C code allowed. - No messageKeys. - There are file size and memory considerations with your Rocky.js projects. If you include a large JS library, it probably won't work. ## SDK 4.0 We have published an updated version of [CloudPebble](https://cloudpebble.repebble.com/) which now supports creating Rocky.js watchfaces in JavaScript. If you prefer our local tools, we've also published everything you need to begin creating Rocky.js watchfaces. Run the following command to install the Pebble Tool and 4.0 SDK: ``` $ brew upgrade pebble-sdk$ pebble sdk install latest ``` ## How to Get Started We created a [2-part tutorial](https://developer.repebble.com/tutorials/js-watchface-tutorial/part1/) for getting started with Rocky.js watchfaces. It explains everything you need to know about creating digital and analog watchfaces, plus how to retrieve weather conditions from the internet. If you're looking for more detailed information, check out the [API Documentation](https://developer.repebble.com/docs/rockyjs). ## Sample Watchfaces We've already created a few sample watchfaces: - [Tictoc](https://github.com/pebble-examples/rocky-watchface-tutorial-part1) - Simple analog watchface. - [Tictoc Weather](https://github.com/pebble-examples/rocky-watchface-tutorial-part2) - Simple analog watchface with weather data from a REST API. - [Leco with Weather](https://github.com/orviwan/rocky-leco-weather) - Simple digital watchface with weather data from a REST API. - [Leco with Clay](https://github.com/orviwan/rocky-leco-clay) - Simple analog watchface which uses Clay for configurable settings. - [Simplicity](https://github.com/orviwan/simplicity-rockyjs) - Rocky.js version of the classic Simplicity watchface. ## Feedback and Help We hope you're as exicited about Rocky.js as we are. If you need assistance, have feedback or just want to send us some love, we're in #rockyjs on Discord or the [Pebble developer forums](https://forums.pebble.com/c/development). We're already working on the next update of Rocky.js and your feedback will help shape the future! * * * --- # Creating Native Companion Apps with JS and Cordova Source: https://developer.repebble.com/blog/2016/07/01/creating-native-companion-apps-with-js-and-cordova/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 01 July 2016 - [Ryan Perry-Nguyen](https://developer.repebble.com/blog/authors/rperryng/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/07/01/creating-native-companion-apps-with-js-and-cordova.md#comments) Creating companion apps using [PebbleKit Android](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) and [PebbleKit iOS](https://developer.repebble.com/guides/communication/using-pebblekit-ios.md) is a great way to extend the functionality of your Pebble watchfaces and apps, but what if Java or Objective C just isn't your thing? What if you love JavaScript? What if you just don't want to write the same companion app twice? Now you don't have to! Enter [Cordova](http://cordova.apache.org/) - an open source framework for building cross-platform mobile apps using web technologies such as HTML5 and JavaScript. What does this mean for you? One framework, and one codebase for building Pebble companion apps for iOS and Android! Cordova allows developers to create plugins that wrap native functionality such as accessing the phone's contacts, calendar, camera, and other phone APIs. Plugins allow you to interface with these APIs through JavaScript, and use them in your Cordova projects. Needless to say, PebbleKit Android and PebbleKit iOS are now available as a singular [Cordova plugin](https://www.npmjs.com/package/cordova-plugin-pebblekit). In this blog post, we'll look at how to use Cordova to create a companion app that allows you to retrieve the next upcoming calendar event for the current day. ## Setup The first step will be installing Cordova and creating a new project. Cordova provides these steps generically [here](http://cordova.apache.org/plugins/?platforms=cordova-android%2Ccordova-ios&sortBy=Downloads), but if you want to follow along down to the T, here are the steps listed below. ``` $ npm install -g cordova $ cordova create calendar-example com.pebble.calendarexample "Calendar Example"$ cd calendar-example $ cordova platform add android $ cordova platform add ios $ cordova plugin add cordova-plugin-calendar $ cordova plugin add cordova-plugin-pebblekit ``` > Note, this assumes you have node/npm already installed and avaiable on your machine. If not, head on over to [nodejs.org](https://nodejs.org/en/) to install node. Doesn't seem too bad! If the `cordova create` command seems a little scary to you, don't be afraid to ask for help with `cordova create --help`. ## Querying Events The next step is to use the calendar plugin we added earlier in order to fetch the next event for the day. Before we do that though, we'll need to to setup the Cordova PebbleKit plugin, as shown below. Fire up your favourite text editor and open `www/js/index.js` file. This is the JavaScript file that runs when your app starts up, your application's control logic will go here. Upon opening the file, you'll notice there's already a fair bit of code generated by Cordova. What's important here is the `onDeviceReady` function, this is a callback you can use to be notified once the plugins for your Cordova project have been loaded and are ready to be called. Let's create our own function called `fetchCalendarEvent` to be called once the PebbleKit plugin has been setup. We'll also declare a variable uuid with our Pebble app's UUID, which is required when communicating with our watchapp via AppMessage. ``` var uuid = "ebc92429-483e-4b91-b5f2-ead22e7e002d";var app = {/\* ... \*/onDeviceReady: function () {app.receivedEvent('deviceready');window.pebblekit.setup(uuid, function () {app.fetchCalendarEvent();});},fetchCalendarEvent: function () {var startDate = new Date(); // Nowvar endDate = new Date();endDate.setHours(24, 0, 0, 0); // Nearest midnight in the futurewindow.plugins.calendar.findEvent(undefined, // titleundefined, // eventLocationundefined, // notesstartDate,endDate,app.calendarSuccessCallback,app.calendarFailureCallback);},calendarSuccessCallback: function (events) {// TODO},calendarFailureCallback: function (err) {console.error('Failed to fatch calendar events', err);}/\* ... \*/} ``` > Note, the calendar plugin recommends [checking that you have sufficient permissions](https://github.com/EddyVerbruggen/Calendar-PhoneGap-Plugin#android-6-m-permissions) before using its APIs. For the sake of brevity, the permission workflow has been omitted in this post. The first 3 parameters we passed to the `calendar.findEvent` function are undefined, since we don't want to filter out events with specific titles, locations, or notes. The last two parameters are callback functions that will be executed when the app has succeeded or failed to fetch calendar data. Our calendarFailureCallback will log a simple message, and our calendarSuccessCallback will sort through the events and find the earliest event in the list. ``` calendarSuccessCallback: function (events) {if (events.length == 0) {// TODOreturn;}// Find the earliest returned eventvar earliestEvent = events[0];for (var i = 1; i \< events.length; i++) {var tempDate = events[i];var date1 = Date.parse(earliestEvent.startDate);var date2 = Date.parse(tempDate.startDate);if (date2 \< date1) {earliestEvent = tempDate;}}}, ``` ## Sending An AppMessage All we have to do now is send the relevent bits of information down to our Pebble app. This is where the Pebble plugin is going to come in handy. At the end of the function, we'll call `sendAppMessage` which works in a similar manner to [PebbleKit JS' sendAppMessage](https://developer.repebble.com/docs/pebblekit-js/Pebble.md#sendAppMessage) function. The title is easy to snag - it's a property on the event object, available via `event.title`. The hours and minutes will take a bit of work since they only exist within a string property on the object (`event.startDate`). Luckily the format for the date is fixed, so we know where in the string it will be. With this in mind, we can use the [String.prototype.substring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring) method to extract the numbers we want. The method takes two parameters, the first is the starting index (inclusive) of the substring, and the second is the the end index (exclusive). So if we want the hour, we'd pass 11 as the starting index, and 13 as the end index. This will create a substring containing the 11th and 12th characters of the string. Also in this example, the `data` object represents our AppMessage, where `10`, `11`, and `12` are the AppMessage keys. ``` // Format is in YYYY-MM-DD HH:mm:ssvar hourString = earliestEvent.startDate.substring(11, 13);var hour = parseInt(hourString);var minuteString = earliestEvent.startDate.substring(14, 16);var minute = parseInt(minuteString);// AppMessage keysvar data = {'10': earliestEvent.title,'11': hour,'12': minute};window.pebblekit.sendAppMessage(uuid, data, function () {console.log('sent AppMessage', data);}, function (err) {console.error('Couldn\'t send AppMessage', err);}); ``` The only thing left is to read the AppMessage on the C side. To quote every university professor I've ever had: "We leave this as an exercise to the reader". (Just kidding, you can see the watchface's full C code [here](https://github.com/pebble-hacks/cordova-plugin-pebblekit/blob/master/example/pebble/src/pebble.c), or consult the [documentation](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md)). While we only used two of the APIs of the PebbleKit Cordova plugin here, there is much more available to you, all with [new and shiny documentation](https://github.com/pebble-hacks/cordova-plugin-pebblekit) For folks that are interested, our Cordova plugin is completely open source, and hosted on [Github](https://github.com/pebble-hacks/cordova-plugin-pebblekit). We welcome bug reports, feature requests, and contributions with open arms :). And that's all there is to it! If you've been holding back on writing a companion app for your amazing app, now's the time to try it! * * * --- # Introducing Clay - App configuration made easy Source: https://developer.repebble.com/blog/2016/06/24/introducing-clay/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 24 June 2016 - [Keegan Lillo](https://developer.repebble.com/blog/authors/keegan/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/06/24/introducing-clay.md#comments) It is with great pleasure that I present to you, Clay, a Pebble package that makes it easy to add offline configuration pages to your Pebble apps. All you need to get started is a couple lines of JavaScript and a JSON file; no servers, HTML or CSS required. ![Clay Example](https://developer.repebble.com/assets/images/blog/2016-06-24-introducing-clay/clay-example.png) ## How to get started This post aims to provide high level explanation of how the framework works and how it came to be. Below is a quick overview of how easy it is to make your app configurable with Clay. If you would like to learn how to integrate Clay into your project, read our guide on [app configuration](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) or read the full documentation on the project [GitHub repository.](https://github.com/pebble/clay#clay) **1) Install the module via [Pebble Packages](https://developer.repebble.com/guides/pebble-packages/using-packages.md)** - SDK: `$ pebble package install pebble-clay` - CloudPebble: Add `pebble-clay`, version `^1.0.0` to your project dependencies. **2) Create a configuration file that looks something like:** ``` module.exports = [{"type": "heading","defaultValue": "Example App"},{"type": "section","items": [{"type": "color","messageKey": "BackgroundColor","defaultValue": "0x000000","label": "Background Color"},{"type": "toggle","messageKey": "Animations","label": "Enable Animations","defaultValue": false}]},{"type": "submit","defaultValue": "Save Settings"}]; ``` **3) Update your project settings with the matching `messageKeys` from the config above.** **4) Add a few lines to your `index.js`** ``` var Clay = require('pebble-clay');var clayConfig = require('./config');var clay = new Clay(clayConfig); ``` **5) Retrieve the values on the C side using standard [app messages](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md) or alternatively let [enamel](https://github.com/gregoiresage/enamel) do the work for you.** ## Why I created Clay Clay began as a side project of mine. Whilst developing my [Segment watchface](https://apps.pebble.com/applications/560ae4754d43a36393000001), I wanted to add a configuration page to allow users to customize the colors of the watchface. However, I soon discovered this process to be rather fiddly. The old way of doing things required you to create and host HTML pages even for simple things like changing a background color or toggling an option. You would also need to write a bunch of boilerplate JavaScript to serialize and send the settings to the watch. Best case, for developers, this is super tedious. Worst case, it is terrifying. By day, I work as a web developer (mainly front-end) so if I was finding the process tiresome, I could only imagine how challenging it would be for someone who wasn't familiar with web technologies. And so I decided to create a framework that would alleviate this barrier for developers. I had a number of requirements that needed to be met in order for the framework to achieve my goals: - It should not require developers to write any HTML or CSS. - It should use JSON to define the generated config page. - It should all work offline - Developers should be able to add interactivity to the config page without manually manipulating DOM. - Developers should be able to create and share their own custom components. - The config page should be able to be versioned with the rest of the code in the project. - It should have extensive unit tests with 100% test coverage. ## How Clay Actually Works Clay has two main components. The first is a very long string that is compiled from all the HTML, CSS and JavaScript that forms the skeleton of the generated config page. The second is the module that gets included in your `index.js`. This module is in charge of bundling all the dynamic elements set by the developer into a format that can be opened using `Pebble.openURL()`. It is also in charge of listening for the `webviewclosed` event and persisting the user's settings to local storage. I use Gulp and a series of Browserify transforms to compile all these components into a single module. The advantage of using a system such as Browserify, is that later, Clay can be made into a stand alone module to be used by developers who have very advanced use cases that require Clay to be run in a hosted HTML page. The most challenging item on the requirements list was making the whole thing work offline. Neither of the Pebble mobile apps allow file system access from the config page's webview, so I had to get a little creative. It turns out that [Data URIs](https://en.wikipedia.org/wiki/Data_URI_scheme) work with more than images. You can in fact encode an entire HTML page into the URI. This was the solution to making the config pages offline. It sounds crazy but this method actually provides other advantages for developers beyond offline access. By bundling all the HTML, CSS and JavaScript into the Clay package, the version of Clay being used by the developer will not change without the developer rebuilding their app. This means developers do not need to worry about the behavior of their app's configuration page potentially breaking as new versions of Clay get released. ## Advanced Use If developers want to add additional functionality to their config page, they can. Clay allows developers to inject, what I call a [`custom function`](https://github.com/pebble/clay#custom-function) into the generated config page. This allows developers control of their config page without needing to manipulate the DOM. Clay achieves this by exposing an API that provides a consistent way of interacting with the config page as well as making AJAX requests. Instead of manually updating the values of HTML elements, developers can use the much simpler Clay API. ## Where to find more information - [App configuration guide.](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) - [Clay GitHub repository including full documentation.](https://github.com/pebble/clay) - Chat to us in the `#clay` channel on Discord. - Visit the [Pebble Forums](https://forums.pebble.com/) - Tweet at [@pebbledev](https://twitter.com/pebbledev) ## How to get involved Clay is open source and welcomes pull requests from anybody. Have a look at the [contributing guide](https://github.com/pebble/clay/blob/master/CONTRIBUTING.md) for instructions on how you can help make Clay even better. Regardless of your skill level, if you have any ideas on how Clay could be improved or if you find any bugs, we would love you to [submit an issue on GitHub](https://github.com/pebble/clay/issues/new). * * * --- # A Wild SDK Appears Source: https://developer.repebble.com/blog/2016/06/15/sdk4-preview/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 15 June 2016 - [Cat Haines](https://developer.repebble.com/blog/authors/cat/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/06/15/sdk4-preview.md#comments) Developers rejoice - we’ve released the first version of our [SDK 4](https://developer.repebble.com/sdk4) developer preview! This SDK enables you to start building applications for the new Diorite platform (Pebble 2), and includes a set of new APIs for interacting with the 4.0 user experience. In this blog post we’ll dive into the [App Glance](https://developer.repebble.com/docs/c/Foundation/App_Glance.md), [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md), and [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md) APIs, and explain how you can use them to create great experiences for your users! ## App Glance API Let’s start with the App Glance API, which allows applications to present information for the launcher to display. The information an application displays in the launcher is called an app glance - and the information that is displayed at any particular point in time is referred to as an [`AppGlanceSlice`](https://developer.repebble.com/docs/c/Foundation/App_Glance.md#AppGlanceSlice). ![Virtual Pet App](https://developer.repebble.com/assets/images/blog/2016-06-15-sdk4-preview/virtual-pet.png) `AppGlanceSlice`s have expiration times, which means you can add multiple slices at once. Slices will be displayed in the order they are added and removed at their specified expiration times. ``` static void prv\_update\_app\_glance(AppGlanceReloadSession \*session, size\_t limit, void \*context) {// This shouldn't happen, but developers should always ensure they have// sufficient slices, or adding slices may fail.if (limit \< 1) return;// !! When layout.icon\_resource\_id is not set, the app's default icon is usedconst AppGlanceSlice entry = (AppGlanceSlice) {.layout = {.template\_string = "Hello from the app glance"},.expiration\_time = APP\_GLANCE\_SLICE\_NO\_EXPIRATION};// Add the slice, and store the result so we can check it laterAppGlanceResult result = app\_gpance\_add\_slice(session, entry);} ``` To dig into this feature, we recommend you start with our new [AppGlance C API guide](https://developer.repebble.com/guides/user-interfaces/appglance-c.md), and also give the [API docs](https://developer.repebble.com/docs/c/Foundation/App_Glance.md) a quick read. > We are planning to extend the AppGlance API to include a PebbleKit JS API, as well as a HTTP web API, intended to allow developers to push `AppGlanceSlice`s from external web services to their users' smartwatches. ## UnobstructedArea API The [`UnobstructedArea`](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md) API allows developers to create watchfaces capable of sensing, and responding to Timeline Quick View events. Timeline Quick View is a new feature in SDK 4.0, which displays upcoming and ongoing events on top of the user’s watchface. The functionality added through the `UnobstructedArea` API allows developers to get the unobstructed bounds of a layer, or subscribe to events related to the watchface’s unobstructed area changing. ``` static int s\_offset\_top\_percent = 33;static int s\_offset\_bottom\_percent = 20;static void prv\_unobstructed\_change(AnimationProgress progress, void \*context) {// Get the total available screen real-estateGRect bounds = layer\_get\_unobstructed\_bounds(window\_layer);// Shift the Y coordinate of the top text layerGRect top\_frame = layer\_get\_frame(text\_layer\_get\_layer(s\_top\_text\_layer));top\_frame.origin.y = bounds.size.h \* s\_offset\_top\_percent / 100;layer\_set\_frame(top\_frame, text\_layer\_get\_layer(s\_top\_text\_layer));// Shift the Y coordinate of our bottom text layerGRect bottom\_frame = layer\_get\_frame(text\_layer\_get\_layer(s\_bottom\_text\_layer));bottom\_frame.origin.y = bounds.size.h \* s\_offset\_bottom\_percent / 100;layer\_set\_frame(bottom\_frame, text\_layer\_get\_layer(s\_bottom\_text\_layer));}static void prv\_main\_window\_load(Window \*window) {unobstructed\_area\_service\_subscribe((UnobstructedAreaHandlers) {.change = prv\_unobstructed\_change}, NULL);} ``` > We encourage developers to begin exploring what their existing watchfaces will look like when the system is displaying the Timeline Quick View dialog, and adjust their designs to provide the best experience possible for users. Take a look at our [UnobstructedArea API Guide](https://developer.repebble.com/guides/user-interfaces/unobstructed-area.md) and the [API documentation](https://developer.repebble.com/docs/c/User_Interface/UnobstructedArea.md) to get started! ## AppExitReason API One of the APIs we haven’t talked about as much is the [`AppExitReason`](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md) API, which enables developers to specify why their app exited, and determines whether the system returns the user to the launcher, or the active watchface. Take a look at our [AppExitReason API Guide](https://developer.repebble.com/guides/user-interfaces/app-exit-reason.md), and read [the API docs](https://developer.repebble.com/docs/c/Foundation/Exit_Reason.md) to learn more. This feature enables developers to create "One Click Action" watchapps, that perform an action, then immediately return the user to the watchface to create a simple, fluid experience. ![Uber App](https://developer.repebble.com/assets/images/blog/2016-06-15-sdk4-preview/uber.gif) To help get you started thinking about and designing One Click Action apps, we’ve created a [One Click Action App Guide](https://developer.repebble.com/guides/design-and-interaction/one-click-actions.md) around the relatively minimal example of locking and unlocking your front door (with a [Lockitron lock](https://lockitron.com)). ## 4.0 Emulator The SDK 4 preview also includes an update to the emulator, which not only adds support for Pebble 2, but includes the updated 4.0 user interface, and a few other goodies that we’re sure developers will love. The new emulator includes a launcher capable of displaying watchapps’ glances, and can be accessed by pressing the `Select` button from the watchface. CloudPebble and the Pebble Tool also include new functionality to enable developers to toggle Timeline Quick View, allowing you to make sure your watchface looks good in every context! Finally, we’ve added the ability to install multiple watchfaces and watchapps into the emulator. Watchfaces and watchapps installed into the emulator will remain installed (even if the emulator is closed) until `pebble wipe` is called from the command line. Due to limitations with CloudPebble, this feature is currently only available in the Pebble Tool. ## What’s Next The SDK 4 developer preview is exactly that, a preview. You’ve probably noticed a few really important and exciting features we didn’t mention - the Heart Rate API has been designed, but is not yet fully implemented, and we have not yet added the ability to build applications for the Emery Platform (Pebble Time 2). The official SDK 4 release is currently planned for the end of August - and it will include not only the Heart Rate API, and support for building Emery applications, but the first version of Pebble’s fantastic new embedded JavaScript SDK, [Rocky.js](http://pebble.github.io/rockyjs/). ## Show Us What You Make While we’ve been conceptualizing and designing the new functionality in SDK 4 for quite a long time, the API itself is relatively new, even for those of us lucky enough to ‘be on the inside’ - so we’ve only really just begun to see what’s possible, and we’re more than excited to see what you’ll build with this new functionality. Send us a link to something you’ve built on Twitter ( [@pebbledev](https://twitter.com/pebbledev)) and we’ll look at including it in an upcoming newsletter (and send some swag your way). Happy Hacking! Team Pebble * * * --- # Introducing Pebble Packages Source: https://developer.repebble.com/blog/2016/06/07/pebble-packages/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 07 June 2016 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/06/07/pebble-packages.md#comments) I am thrilled to announce that, as part of SDK 3.13 and Pebble Tool 4.3, we have launched our own packaging system: Pebble Packages! There are already a healthy selection of Pebble libraries to be had, such as Yuriy's excellent [EffectLayer](https://github.com/ygalanter/pebble-effect-layer), Reboot's Ramblings' [palette manipulator](https://github.com/rebootsramblings/GBitmap-Colour-Palette-Manipulator), or even our own [weather library](https://github.com/pebble-hacks/owm-weather). However, using them today is inelegant: you generally have to copy/paste all of the library code into your own project and follow some other setup instructions: assigning appmessage keys, editing resources into your appinfo.json, or similar. Starting today, using a Pebble Package can be as simple as running `pebble package install pebble-owm-weather`. A Pebble Package can contain both C code for the watch and JavaScript code for the phone. Furthermore, it can also contain any resources, and define its own appmessage keys. All numeric identifiers will be resolved at app build time to ensure there are never any conflicts. This helps to enable zero-setup packages of all kinds. To make the usage of appmessages in Pebble Packages possible, we have also substantially improved the functionality of the old `appKeys` section of appinfo.json. We will now automatically insert the keys into your C code with the prefix `MESSAGE_KEY_`. You can also access their numeric values from JavaScript by requiring the `message_keys` module: `var keys = require('message_keys')`. Packages would be nothing without a package manager, so we have built our packaging system on top of the excellent [npm](https://npmjs.com) package manager. This gives us support for dependency management, versioning, package hosting, and more. Furthermore, some traditional JavaScript modules on npm will work out of the box, as long as they don't depend on running in node or on a browser. As part of this move we have **deprecated appinfo.json** : we now use `package.json` instead. The latest version of the Pebble Tool can convert your project when you run `pebble convert-project`. Old-style projects continue to be supported, but cannot use the package manager. For your convenience, we have provided some wrappers around npm functionality: - `pebble package install` to safely install a package. - `pebble package uninstall` to uninstall a package. - `pebble package login` to log in to or create your npm account. - `pebble package publish` to publish your package to npm. We also have UI in CloudPebble to enter your dependencies. You can browse the available packages on npm under the [pebble-package](https://www.npmjs.com/browse/keyword/pebble-package) keyword, and read our guides on [using](https://developer.repebble.com/guides/pebble-packages/using-packages.md) and [creating](https://developer.repebble.com/guides/pebble-packages/creating-packages.md) Pebble Packages. I'm looking forward to seeing what you make! * * * --- # Time to Expand Our Core Business!! Source: https://developer.repebble.com/blog/2016/05/24/Kickstarter-3/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 24 May 2016 - [Cat Haines](https://developer.repebble.com/blog/authors/cat/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/05/24/Kickstarter-3.md#comments) You might have noticed a bit of a buzz around Pebble today - we have officially launched our **[third Kickstarter campaign](https://pebble.com/kickstarter)**, and it is jam packed with exciting new products and features for developers to explore and hack on. We have been hard at work not only creating the next generation of health-focused smartwatches, but also refining what great wearable user and developer experiences look like. We are introducing an entirely new class of wearable device, and adding a modern JavaScript SDK that takes advantage of the Web APIs JavaScript developers around the world know and love. There is a lot to take in, so we’re here to help break down what’s new and exciting for our favorite developer community! ## The Smartwatches We are introducing two new smartwatches designed to track and improve your health, and simplify your life. ![Pebble 2, and Time 2](https://developer.repebble.com/assets/images/blog/2016-05-24-kickstarter-3/smartwatches.png) Pebble 2, and Time 2 Pebble 2 is a take on our classic Pebble that launched the smartwatch market in 2012. It includes updated hardware with a high contrast black and white display, microphone, up to seven days battery life, plus activity, sleep and heart rate tracking. Time 2 takes everything our users love about the Pebble Time Steel, and builds upon it. It offers the same ten day battery life, activity and sleep tracking you’re used to, and adds a larger color display, a smaller bezel, and heart rate tracking! If you are looking to dive a bit deeper, we encourage you to check out our updated [Hardware Information Guide](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md) below, and our [Pebble-3D GitHub repository](https://github.com/pebble/pebble-3d) that includes 3D drawings of all our smartwatches! | Company | Pebble Technology Corporation | Core Devices | | --- | --- | --- | | Model | Classic, Steel | Time, Time Steel | Time Round | Pebble 2 | Pebble 2 Duo | Pebble Time 2\* | Pebble Round 2 | | --- | --- | --- | --- | --- | --- | --- | --- | | Platform | Aplite | Basalt | Chalk | Diorite | Flint | Emery | Gabbro | | SOC | [STMicro STM32F205RE](https://www.st.com/en/microcontrollers-microprocessors/stm32f205re.html) | [STMicro STM32F411](https://www.st.com/en/microcontrollers-microprocessors/stm32f411.html) | [Nordic nRF52840](https://www.nordicsemi.com/Products/nRF52840) | [SiFli SF32LB52J](https://www.sifli.com/en/node/31) | | CPU | Cortex-M3 64 MHz | Cortex-M4 100 MHz | Cortex-M4 64 MHz | [Star-MC1](https://www.armchina.com/mountain?infoId=160&name=%22%E6%98%9F%E8%BE%B0%22%20STAR-MC1&CategoryStr=%22%E6%98%9F%E8%BE%B0%22&pStatus=) (Cortex-M33-like) 240 MHz | | Max Resource Size | 96k | 256k | | Max App Size (code + heap) | 24k | 64k | 128k | | Display Shape | Rectangle | Round | Rectangle | Round | | Display Size | 1.26" | 1.25" | 1" | 1.26" | 1.5" | 1.3" | | Display Resolution | 144 x 168 | 180 x 180 | 144 x 168 | 200 x 228 | 260 x 260 | | Display PPI | 175 | 182 | 175 | 202 | 200 | | Display Colors | 2 _(B/W)_ | 64 _(Color)_ | 2 _(B/W)_ | 64 _(Color)_ | | Display Manufacturer | Sharp _(LS013B7DH05)_ | JDI _(Color)_ | JDI _(Color)_ | Sharp _(LS013B7DH05)_ | JDI _(LPM015M135A)_ | Sharp _(LS013B7DD02)_ | | Touch Screen | No | Yes | | Backlight | White LED | Multicolor RGB LED | White LED | | Heart Rate Monitor | No | No _(possible with smartstrap)_ | Yes _(except SE model)_ | No | Yes | No | | Microphone | No | Yes | Yes _(with 2nd Mic for ENC\*1)_ | | Speaker | No | Yes | No | | Sensors | Accelerometer, Compass | Accelerometer | 6-axis IMU, Compass, Barometer | 6-axis IMU, Compass | 3-axis IMU, Compass | | Buttons | 4 | | Vibration | ERM (eccentric rotating mass) | LRA (linear resonant actuator) | | Case Material | Polycarbonate _(P, PT)_ Stainless Steel 316 _(PS, PTS)_ | Stainless Steel 316 | Polycarbonate | Stainless Steel 316 | | Lens Material | Polycarbonate | Gorilla Glass _(curved)_ | Gorilla Glass _(flat)_ | Hardened Glass _(flat)_ | Hardened Glass _(curved)_ | | Charging Port | Power only | Smart accessory port | Smart accessory port _(data only)_ | Power only | | Battery Life\*4 | ~7 days | ~7 days (Pebble Time), ~10 days (Pebble Time Steel) | ~2 days | ~7 days | ~30 days | ~14 days | | Water Resistance\*3 | 5 ATM\*2 _(50 m)_ | 3 ATM\*2 _(30 m)_ | None _(Splash Resistant)_ | 3 ATM\*2 _(30 m)_ | 20 m | TBD _(target IPX8)_ | TBD _(target 30m)_ | | Model | Classic, Steel | Time, Time Steel | Time Round | Pebble 2 | Pebble 2 Duo | Pebble Time 2\* | Pebble Round 2 | | --- | --- | --- | --- | --- | --- | --- | --- | \*1 ENC = Environmental Noise Cancellation \*2 Please note regarding water resistance: - 3 ATM → Safe for hand-washing, rain, splashes, and brief immersion, but not for swimming/diving. - 5 ATM → Safe for swimming in shallow water, showering, and snorkeling, but not for diving. \*3Rated waterproofing is applicable to new, sealed hardware. Age or repairs may compromise water resistance. \*4Rated battery life is applicable to new hardware in typical usage conditions. Heavy use or use of power intensive apps and watchfaces may lead to a lower battery life. Battery life will reduce as the watch ages. Aftermarket replacement batteries may have a lower capacity. ## Pebble Core A lot of people like to call Pebble a ‘smartwatch’ company, but we’ve decided it’s _Time_ to prove them wrong, and expand our _Core_ business with an entirely new ultra-wearable! ![Pebble Core](https://developer.repebble.com/assets/images/blog/2016-05-24-kickstarter-3/core.jpg) Pebble Core Pebble Core is an entirely new class of wearable device - it features 3G, WiFi, Bluetooth, GPS, an accelerometer, two programmable buttons, a headphone jack, and a hardware expansion port accessible to developers. **Oh yeah, and it’s an open platform.** Pebble Core is built on top of Android, but has the familiar Pebble developer experience you’re used to. Pebble Core will be able to run the PebbleKit JS component of watchapps, to ensure apps continue functioning when a user leaves their mobile device at home. Additionally, developers will be able to write standalone Pebble Core applications using an extended version of PebbleKit JS, allowing you to customize the user experience depending on whether or not the user has an attached Pebble smartwatch. With Internet connectivity on the go, and programmatic access to GPS and Bluetooth Low Energy, the possibilities are endless. - _Create an app to open your Internet-connected garage door with the click of a button, or order pizza to your current location._ - _Use geofencing to automatically notify your loved ones when you leave work or school._ - _Hook it up to your drone and send real time sensor and location information to your phone._ We have a million ideas for Pebble Core, and we can’t wait for you to get your hands on it and start hacking! We will be providing more information and news around Pebble Core soon, so stay tuned! ## SDK 4.0 (The C Part) With the launch of Pebble 2 and Time 2 we will be releasing our next revision to the operating system with Pebble OS 4.0. The new operating system includes an updated user experience aimed at helping the user have access to the information they need at a glance, and take quick, simple actions. Let’s look at some of the new features in Pebble OS 4.0, and how you can take advantage of them as a developer. The most obvious change is that we have replaced timeline past with an updated Launcher capable of displaying glanceable information through a new AppGlances API. To help accommodate the removal of timeline past, we’ve updated the behaviour of “Now” pins to allow them to persist into the future. ![Updated Launcher](https://developer.repebble.com/assets/images/blog/2016-05-24-kickstarter-3/launcher.gif) Preview version of updated launcher with AppGlances Along with the updated launcher, we’re also helping users gain better insight into what’s going on right now through a new feature called timeline quick view. Timeline Quick View is a small notification displayed on the user’s watchface to let them know what’s happening right now, or coming up soon. [![Timeline Quick View](https://developer.repebble.com/assets/images/blog/2016-05-24-kickstarter-3/peek.png)](https://developer.repebble.com/assets/images/blog/2016-05-24-kickstarter-3/peek.png) Preview version of timeline quick view The default behaviour for timeline quick view is to simply draw over the bottom of the watchface, however, developers will have access to a new set of APIs to manage how their watchface responds to timeline quick view events. SDK 4.0 will include APIs to find the “unobstructed area” of the display, and to react to timeline quick view being displayed, and hidden. Using these APIs, developers can create watchfaces that respond to timeline quick view, and programmatically adjust! And of course, you’ll have programmatic access to the heart rate monitor through an updated HealthService, capable of providing both real time, and historical heart rate information. ## SDK 4.0 (The Embedded JavaScript Part) We’ve been hard at work enabling developers to write JavaScript applications that run directly on our smartwatches. In a recent firmware release, we hit a major milestone - we shipped a JavaScript version of the default watchface, TicToc, and a Pebble-specific port of an [embedded JavaScript runtime](https://github.com/pebble/jerryscript) to power it. We believe that providing an embedded JavaScript SDK will make our platform more accessible, powerful, and collaborative. Our JavaScript SDK will be based on existing Standard Web APIs wherever possible - your display will be a [Canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API), your watch and mobile device will communicate through a message-passing API similar to [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers), and you’ll access the compass and accelerometer through the [Device Orientation](https://developer.mozilla.org/en-US/docs/Web/API/Detecting_device_orientation) API. ## This is Real (Really!) We’ll be shipping an early preview version of the 4.0 C SDK near the conclusion of the Kickstarter campaign. The 4.0 C SDK will include preview APIs for managing what information your watchapps display in the launcher, how your watchfaces respond to timeline quick view events, and more. Support for building and testing Pebble 2 and Time 2 watchapps and watchfaces will be included in the first official SDK 4 release, tentatively scheduled for July 2016. Finally, the embedded JavaScript SDK is scheduled to ship later this summer. We want to ensure that you have access to it before the first Pebble 2 smartwatches make it onto users’ wrists, so you have time to write the first wave of Pebble 2 watchfaces in JavaScript! ## Let’s Meetup IRL If you’re local to San Francisco, we welcome you to join us at SF Geekdom on June 15 at 6:00pm (more details to follow) to checkout our new hardware, and learn more about what’s new, and how to best use it. If you are like the majority of the world and don’t live in San Francisco, you’ll be able to access a recording of our Meetup after the event so you can join in the fun and learn what’s new! If you run a Pebble Meetup, or are interested in running an event to bring together Pebble Developers, let us know - we’d love to call-in, talk about what’s new in SDK 4.0, and answer questions from your local community! ## We Need You to Make This Happen We have worked hard to pack this Kickstarter with as many developer friendly features and products as possible, and we hope you are as excited as we are! We’re counting on your support to make all of this happen - so go treat yourself to some awesome new Pebble products, and help us show the world what wearables should look like! [https://pebble.com/kickstarter](https://pebble.com/kickstarter) * * * --- # Guest Blog Post - PAL Smartstrap Source: https://developer.repebble.com/blog/2016/05/18/PAL-Smartstrap/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 18 May 2016 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2016/05/18/PAL-Smartstrap.md#comments) _This is a guest blog post from Powerstrap, the team behind Pal, a GPS and battery extending smartstrap that launched on Kickstarter this week:_ [kickstarter.com/projects/655382546/pal-strap-gps-and-extended-battery-strap-for-pebbl](https://www.kickstarter.com/projects/655382546/pal-strap-gps-and-extended-battery-strap-for-pebbl) Powerstrap is incredibly excited to share that our Kickstarter for the world’s first GPS and battery Smartstrap for Pebble Time and Pebble Time Steel watches is now live! We have been working very closely with Pebble to provide the best strap for this amazing community since 2014 and the time (pun intended) is finally here to share it with everyone! ## Meet Pal! ![Pal Smartstrap](https://developer.repebble.com/assets/images/blog/2016-05-18-PAL-Smartstrap/pal.jpg) It features a high performance, independent GPS that allows users to track speed, distance, elevation, and route for up to 24 hours without the need for a Smartphone. Begin your activity as soon as you go out, with no time wasted idly waiting for a signal. Pal uses industry leading GPS (Qualcomm SiRF), which provides quick signal pickup. It also extends the life of Pebble Time and Pebble Time Steel for up to seven days and shares the same charger for hassle-free charging. In cases of emergency, the Pal can be used as a battery back-up to the Pebble. It also comes with a companion app for iOS and Android for GPS logs and social media sharing. ## How Did We Get Started On This Journey? We love our Pebbles and wanted to contribute to the community. I have been a watchstrap creator, watch straps are my passion. I’ve found that they usually don’t get the respect that they deserve in the creation process. So when I fell in love with Pebble, I wanted to see what I could do to lend my expertise to make the already amazing Pebble just that much more awesome. I took my experience with watch straps, enlisted two great engineers CC and Edwin, whose backgrounds are in electronics, mechanical engineering and the battery business. One of the things that was so incredibly helpful, was being able to work hand in hand with Pebble to make sure that our idea for a functional strap matched with Pebble’s vision and community. They have been great partners in helping us identify what would be of most use to the Pebble community, best practices, and gave great feedback which helped us perfect the design. This relationship is not unlike the relationship between the Pebble and Pal, both working together in harmony to make something unique and great -- which actually helped inspire the name Pal! ![PAL Concept and Prototype](https://developer.repebble.com/assets/images/blog/2016-05-18-PAL-Smartstrap/concept.png) Early concept drawing and battery buckle prototype ## So how does it work exactly? Let’s dive in deeper into the technical specification of the Pal Smartstrap. Pal GPS architecture is powered by Qualcomm’s SiRF chipset, this chipset can track speed, distance, and elevation as a standalone, offering an impressively fast first time signal pickup within three minutes and it will continue to work even if it goes indoors for a while, and then comes back outside. PAL also features data storage for up to 24 hours of GPS data. ![GPS Communication](https://developer.repebble.com/assets/images/blog/2016-05-18-PAL-Smartstrap/communication.jpg) Raw GPS data received from satellites are processed by Pal’s MCU and results are pumped to Pebble for display and storage - allowing the smartwatch to leverage GPS location data without the need to be connected to a mobile phone. Per our efficient power management intelligence, Pal’s battery recharges Pebble’s battery only when the user manually activates the function or when Pebble’s battery drops to very low level and triggers auto-charging. Connecting pins between the Pal and Pebble are surrounded by soft gasket. This structure makes Pal water resistant, but it probably cannot withstand swimming or diving. In any case, even if water goes between connecting pins, Pal’s electronic design has protection measure and it won’t be damaged. ![Exploded View of PAL](https://developer.repebble.com/assets/images/blog/2016-05-18-PAL-Smartstrap/exploded-pal.jpg) **Tech Specs:** GNSS support: GPS only TTFF cold fix time: about 40 seconds under open sky. Location fix time can be accelerated by AGPS (working in progress with Qualcomm) Location hot fix: within 10 seconds. Certification planned to be done upon campaign success and product launch: CE, FCC, IC Battery technical Specs: Battery capacity: 250mAh Battery chemistry: Lithium Ion Polymer Nominal Voltage: 3.8V Pebble Watch’s battery: can be charged from either PAL’s internal battery or external charging port underneath PAL Internal battery of PAL: charge up from charging port underneath PAL ## Design Together, the Pal and Pebble Time weigh only 65grams and offer users enhanced functionality, without added weight or bulkiness. The interchangeable design features a unique strap curvature that is ergonomically engineered to fit every wrist size and it only takes seconds to pin the Smartstrap to the clasp to customize the strap length. ## Open Platform From the beginning, it was important that we kept Pal open so developers have the opportunity to take the strap to the next level. We hope that by incorporating the best in class hardware components into Pal, it will help encourage app developers to be even more creative and limitless. Obviously our first priority is getting fully funded so we can go to production, however, we are currently starting communication with interested developers as there are so many interesting and helpful potential ways that this technology can be adapted. When we are fully funded, we will release our Pal strap API. How we interact with developers moving forward will depend on the interest and size of the group, however anticipate that the dev community on Pebble.com as a likely option. If you have an idea for an app for the Pal, we would love to hear from you and potentially support your development. Contacts us here: [pal@palstrap.com](mailto:pal@palstrap.com) We've launched a Kickstarter to help bring PAL to life. We hope you will check us out and support us at [kickstarter.com/projects/655382546/pal-strap-gps-and-extended-battery-strap-for-pebbl](https://www.kickstarter.com/projects/655382546/pal-strap-gps-and-extended-battery-strap-for-pebbl) * * * --- # Pebble.js Support for Chalk! Source: https://developer.repebble.com/blog/2016/05/16/Pebble-JS-Round/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 16 May 2016 - [Meiguro](https://developer.repebble.com/blog/authors/meiguro/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/05/16/Pebble-JS-Round.md#comments) After many morning coding sessions on the weekends, [Pebble.js](https://developer.repebble.com/docs/pebblejs/) has been updated to support the Chalk platform (Pebble Time Round). This update is available in [CloudPebble](https://cloudpebble.repebble.com/), and ready for you to work with right in your browser with no setup! Supporting Chalk required a ton of bug crushing to become compatible with the new 3.0 style status bar, changes to accommodate reduced memory on the 3.x Aplite platform, adding new features such as text flow and paging, and making it easier to target multiple platforms by adding a feature detection API. ![Pebble.js Screenshots](https://developer.repebble.com/assets/images/blog/2016-05-13-pebble-js-round/pebblejs.png) Pebble.js running on all available platforms. Whether or not you’re targeting the Chalk platform, there are many new features for you to explore in this update - and simply recompiling your project with the latest version of Pebble.js will update the look and feel through refined spacing between text fields. The background color of window's status bar can now be changed, allowing it to blend in with the app's background in a similar way to Pebble's Music app. ``` var defaultBackgroundColor = 'white';var defaultColor = 'black';var card = new UI.Card({backgroundColor: defaultBackgroundColor,status: {color: defaultColor,backgroundColor: defaultBackgroundColor}});card.show(); ``` Two new elements were added, [Line](https://developer.repebble.com/docs/pebblejs/) and [Radial](https://developer.repebble.com/docs/pebblejs/), and all elements now include [borderWidth](https://developer.repebble.com/docs/pebblejs/), [borderColor](https://developer.repebble.com/docs/pebblejs/) and [backgroundColor](https://developer.repebble.com/docs/pebblejs/) properties (except `Line`, which uses [strokeWidth](https://developer.repebble.com/docs/pebblejs/) and [strokeColor](https://developer.repebble.com/docs/pebblejs/) instead). There are many bugfixes as well. Scrolling is no longer jittery for cards and menus, the status bar will no longer disappear upon changing windows that both requested to have a status bar, and large bodies of text are truncated in cards instead of just not showing up. There is one new known issue - which causes some applications with heavy memory usage to crash. If you're experiencing this issue, we would appreciate you adding more details to [#161](https://github.com/pebble/pebblejs/issues/161). This update also comes with two new guides to help familiarize yourself with the exciting new world of round and colorful apps. [Using Color](https://developer.repebble.com/docs/pebblejs/) will help you understand all the different ways you can specify which color you want your text (and other elements) to be, and how you would go about styling your app with color. [Using Feature](https://developer.repebble.com/docs/pebblejs/) will help you understand the new Feature API, how it can be used to specify different behaviours and UIs depending on what platform you're running on, and what features it includes. ## CloudPebble Enabling Chalk support in CloudPebble is simple. Open your project's "Settings" screen, check the "Build Chalk" box, then recompile your project. ![CloudPebble Settings Screen](https://developer.repebble.com/assets/images/blog/2016-05-13-pebble-js-round/settings.png) Cloud Pebble Settings Screen. To run your project on CloudPebble's Chalk emulator, compile your project, then open the "Compilation" sceen, and select "Emulator" then "Chalk." ## Pebble Tool + Local SDK If you're using the Pebble Tool and local SDK, you'll need to merge the `master` branch of the Pebble.js repositoroy into your project, then edit the `appinfo.json` file to include `chalk` in the `targetPlatforms` array. ``` { "uuid": "8e02b5f5-c0fb-450c-a568-47fcaadf97eb", "shortName": "Test", "longName": "Test Application", "companyName": "#makeawesomehappen", "versionLabel": "0.1", "sdkVersion": "3", "enableMultiJS": true, "targetPlatforms": ["aplite", "basalt", "chalk"], "watchapp": { "watchface": false }, "appKeys": { }, "resources": { } } ``` To run your project with command line emulator, compile your project, then install it to the Chalk emulator: ``` > pebble clean > pebble build && pebble install --emulator=chalk --logs ``` Remember, we’re also working on supporting native JavaScript applications on Pebble smartwatches, and are calling it [Rocky.js](https://pebble.github.io/rockyjs/). Rest assured, I will continue to maintain Pebble.js, and you can continue to develop and ship apps with it! Keep an eye out on future developments by following the [GitHub repository](https://github.com/pebble/pebblejs) and checking out the latest commits. Let us know abour Pebble.js project or anything else you feel like mentioning, by shouting on Twitter to [@PebbleDev](https://twitter.com/pebbledev), [@Pebble](https://twitter.com/pebble) or even myself [@Meiguro](https://twitter.com/meiguro). Here’s to more exciting weekend coding sessions in the future for both of us! * * * --- # Guest Blog Post - Learning C with Pebble, The Book Source: https://developer.repebble.com/blog/2016/05/11/Learning-C-with-Pebble-The-Book/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 11 May 2016 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2016/05/11/Learning-C-with-Pebble-The-Book.md#comments) _This is a guest blog post from Mike Jipping, professor of Computer Science at Hope College (Michigan, USA), who has been writing an open source book which teaches the C programming language using Pebble smartwatches._ If you want to learn to program using the C programming language, you have lots of choices. C has been around a while, so there are many textbooks and lots of Web sites that have information on the language. Most are very general, with examples and projects that you could run on any general purpose computer. Very few are tailored to embedded systems and none are targeted at smartwatches, until now, that is. Pebble has teamed up with a college professor and a group of Pebble community developers to produce [Learning C with Pebble](http://pbl.io/cbook), an online, open source textbook that teaches C with examples and projects focused on Pebble smartwatches. ![book-cover](https://developer.repebble.com/assets/images/blog/06052015-book-cover.png) In order to build apps for Pebble smartwatches, a developer needs to program in one of two languages: JavaScript or C. Of the two, you might choose JavaScript for some familiar features, but C might be the better choice since it produces apps that run more efficiently and perform with better speed than those written in JavaScript. The book is a textbook: it teaches how to program using the C programming language. But it is very unique among textbooks. - It is free, online, and open source. You can have your own copy for free. You can read it online or download the PDF or ePub versions. - It is written with Markdown formatting. This means you can fork the book's source, edit it or add new content in Markdown, and submit a pull request to have your changes added to the book. - It represents a work in progress. The online version will be updated to reflect error corrections and new content. Sections of the book will be released in intervals. In addition to these unique features, _Learning C with Pebble_ comes with Project Exercises at the end of each chapter. Lots of C books include questions or small exercises to work on with each chapter. This book has "project" exercises: complete Pebble smartwatch applications that give you code to start, ask you to make changes, and give you a solution so you can compare your answer to others. It encourages you to work in focused code that runs on a Pebble and helps you learn C in an environment that is targeted to Pebble programming. In addition to learning the topics in each chapter, these Project Exercises help you learn the Pebble programming system and the scaffolding that apps for Pebble need to run. You learn Pebble code in a way that helps you to become comfortable with the runtime environment and with unfamiliar code. And you learn C along the way. This book is written by Mike Jipping, a professor of Computer Science at Hope College in Holland, Michigan, USA. Mike has been writing apps for handheld computers, smartphones, and wearable devices for over 30 years. He has developed several apps for Pebble smartwatches. The Pebble community developers who are helping out with the book are [Allan Findlay](https://twitter.com/allanf175), [Paul Niven](https://twitter.com/NiVZ), [Mathew Reiss](https://twitter.com/mydogsnowy), [David Rodriguez Rincon](https://twitter.com/dabdemon), and [Rob Spiess](https://twitter.com/robisodd). Everyone is active in the Pebble developer community, in the forums, and on Slack. The book can be found at [http://pbl.io/cbook](http://pbl.io/cbook). The first 8 chapters have been released with the remainder of the book will be coming out over the coming summer. This is a readable book, packed with examples and code and projects. It's a great way to learn the C programming language on Pebble smartwatches. We'd appreciate your feedback, be sure to tweet [@PebbleDev](https://twitter.com/PebbleDev) on Twitter or find us on Discord. * * * --- # Guest Blog Post - Pebble Powered Instruments at GrizzHacks Source: https://developer.repebble.com/blog/2016/04/27/jeremy-karbowski-grizzhacks/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 27 April 2016 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2016/04/27/jeremy-karbowski-grizzhacks.md#comments) _This is a guest blog post from [Jeremy Karbowski](https://github.com/jkarbows), who created a theremin-like gesture based instrument for the Pebble smartwatch at [GrizzHacks 2016](http://grizzhacks.com)._ Hackathons are wonderful. The three I've attended thus far have been amazing opportunities to learn new technologies and work with other hackers to bring an idea to life. Hackathons embody some of the pure magical joy of computing that make your eyes light up with new ideas, and they provide an opportunity for participants to work with hardware and resources that they may not have had access to otherwise. [![Prototyping](https://developer.repebble.com/assets/images/blog/2016-04-26-pebble-theremin/working.png)](https://developer.repebble.com/assets/images/blog/2016-04-26-pebble-theremin/working_full.png) Working on the prototype during GrizzHacks When I showed up to GrizzHacks I had no idea what I wanted to make, but I knew I wanted to explore some of the new hardware that was provided by the organizers, and the Pebble smartwatch and [Myo armband](https://www.myo.com/) immediately stuck out as bursting with potential. I remembered a project from a past hackathon that used the Myo wristband to control a string of colored lights based on the user’s movement, and was impressed by the expressive potential that gesture-based input suggested. I wanted to explore that. Our minds simultaneously inhabit physical and virtual spaces, switching between the two constantly throughout the day. Using movement and gesture-based technology for control draws these spaces closer together, providing a stronger connection between the two worlds and enabling expressive and emotional interactions with virtual spaces through technology. One of the best ways to translate and express emotion is through music, and I realized that with the gesture recognition of the Myo and the accelerometer packed inside of the Pebble I could create an instrument that allowed for musical expression through the motion of the device in a way similar to the [theremin](https://en.wikipedia.org/wiki/Theremin). Now that I had my idea, I just needed to build it. I created a system to stream hardware sensor data to a Node.js server and a web front-end that listeners can connect to from anywhere in the world. Here, streaming sensor data is converted to synthesizer modulations done in-browser by a web audio synth. Movements on different axes were meant to control different parameters of the synthesizer, such as frequency and amplitude. Pebble made this task a dream - the hardware worked beautifully, and I was able to transfer accelerometer data over a websocket connection with ease. ``` // Simplified Pebble JS codevar UI = require('ui');var Accel = require('ui/accel');var ws = new WebSocket('ws://boiling-fjord-40795.herokuapp.com/');Accel.on('data', function(e) {ws.send(JSON.stringify(e.accel));});ws.onclose = function() {Accel.on('data', function(e) { /\* do nothing \*/ });}; ``` After ~20 hours of fiddling with server configurations and synth modules I had a working prototype. A few tweaks to the sensitivity/responsiveness (that feel much better in newer builds) and it was ready to present. Things weren’t perfect, but such is the nature of a hackathon. Larger and excited motions would cause synthesizer values to fluctuate wildly, creating an audio stream that sounded something like a dial-up connection. As a result, I believe I may have the honor of saying that I'm the first person to perform dial-up on stage. The positive response was amazing, the crowd loved it. So much so that I hurried off-stage in a rush of excitement and thanks, forgetting to speak to the very real future of the project. [![Live Demo](https://developer.repebble.com/assets/images/blog/2016-04-26-pebble-theremin/demoing.png)](https://developer.repebble.com/assets/images/blog/2016-04-26-pebble-theremin/demoing_full.png) Demoing the instrument to a live audience Work has begun on expanding the project. The web app design makes it trivial to connect multiple users all playing separate instruments, enabling multi-user performances with each participant playing remotely from anywhere in the world. The viewer end of the interface is modeled as a visualization of a garden, with viewers represented as flowers in the garden and performers as bouncing balls of light. Viewers can chat through a common interface, help shape the current performance through their interaction, as well as record, play back, and share performances. [![Test Interface](https://developer.repebble.com/assets/images/blog/2016-04-26-pebble-theremin/testing.png)](https://developer.repebble.com/assets/images/blog/2016-04-26-pebble-theremin/testing_full.png) Garden's demo interface Development has begun on a new instrument interface powered by an Android application. The app works similarly to the interface created for the Pebble - streaming sensor data from the phone to the web app, with additional concerns and design changes due to the weight of the phone and the addition of a touch screen. The differences in the two will make for two distinctly different instruments with similarly intuitive styles of control, enabling users to join together for collaborative, physically-based musical performances. Huge thanks to the people at Pebble for their great piece of wearable tech and for being all-around awesome. This couldn’t have happened without their awesome documentation and support. I'm incredibly excited for the future of this project. It’s been a blast to work on so far, and I hope you'll join me later this summer when the new version of the site goes live for testing! If you’re interested, you can find more information about the project (including it’s source) on [hackaday.io](https://hackaday.io/project/10876-garden) and follow me on Twitter [@JeremyKarbowski](https://twitter.com/JeremyKarbowski) where I occasionally post about the projects I’m working on. * * * --- # Guest Blog Post - Introducing KiezelPay! Source: https://developer.repebble.com/blog/2016/04/06/KiezelPay/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 06 April 2016 - [Cat Haines](https://developer.repebble.com/blog/authors/cat/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2016/04/06/KiezelPay.md#comments) _This is a guest blog post from [Kiezel's](https://www.kiezelwatchfaces.com/) Kristof (in Belgium) and Jaron (in the US), who recently opened their KiezelPay payments platform to all Pebble developers! We're super excited to see how it will be used and adopted! You can find a list of KiezelPay Powered [watchapps](https://apps.getpebble.com/en_US/collection/kiezelpay/watchapps) and [watchfaces](https://apps.getpebble.com/en_US/collection/kiezelpay/watchfaces) in Pebble's appstore._ [KiezelPay](https://kiezelpay.com) was designed to help developers sell their apps and watchfaces for the Pebble smartwatch as easily, securely, and globally as possible. We learned a lot when we created a payment system for our Kiezel Watchfaces, and we wanted to improve that system and open it to all developers! **We strongly believe you deserve to get paid.** As a team we felt that bringing a solid payment system to the Pebble ecosystem would not only increase developer interest but also bring a much greater quality of apps and watchfaces to Pebble users. ## How Does it Work? KiezelPay apps have 3 possible statuses - unlicensed, trial, and licensed. When the app is compiled with the trial setting enabled, it will check its status on our server as soon as the KiezelPay library is initialized for the first time after a clean install - otherwise, the app will check its status when told to do so by the developer (e.g. [PinyWings](http://apps.getpebble.com/en_US/application/54e69569ad38b6399f000017) checks for the app's status after the first level, allowing users to play the first level before purchasing the game). [![KiezelPay Flow](https://developer.repebble.com/assets/images/blog/2016-04-05-kiezelpay/kiezelpay.png)](https://developer.repebble.com/assets/images/blog/2016-04-05-kiezelpay/kiezelpay.png) The diagram above describes the flow of communication between the Pebble app and the KiezelPay servers, including what it looks like when the optional 'trial' and 'periodic rechecks after purchase' features are enabled. ## Why Use KiezelPay? KiezelPay is secure, simple to integrate, and jam packed with features like trial based apps and watchfaces, discount/promo codes, a day-­to-­day sales dashboard, worldwide transactions, and handles international taxes with ease! ### Security The first thing that comes to mind when thinking about a payment system is security. Whatever solution we created, we knew it had to be difficult to manipulate the system into giving away free usage of the app, and that the payments should be performed in a secure and trustworthy environment. We ensure that all vital information and critical security checks happen in the application's C code, and that all communication with the KiezelPay servers use checksums, app secrets, and [nonces](https://en.wikipedia.org/wiki/Cryptographic_nonce) to help secure the system. _We put a lot of effort into securing our platform, and if you would like to learn more we invite you to contact us at [support@kiezelpay.com](mailto:support@kiezelpay.com)._ ### Taxes/Legal International tax laws are becoming increasingly difficult to handle. Many governments have noticed that the digital goods sector is growing rapidly, and they are losing a lot of money because foreign companies can sell their digital goods with easy without having to pay local sales tax. For these reasons, the EU recently changed their digital goods tax system, which now requires everyone to charge VAT to anyone from the EU buying digital goods from them, regardless of where the company/person selling those goods is located, and regardless of the amount sold... and many more countries are either following suit, or creating new tax requirements for digital goods of their own. Trying to stay on top of tax requirements is an insurmountable burden for a single developer that just wants to make a few bucks selling their apps. At KiezelPay, we have spent an enormous amount of time with international accountants and lawyers to ensure we're compliant with current international taxes and laws - not only for accepting payments, but getting the developer paid legally and correctly in a timely manner. ### Ease of Integration Pebble goes to great lengths to make their development platform accessible to new and experienced developers alike, and we wanted to ensure our platform was the same. An average developer should be able to setup KiezelPay and integrate it into their app in about an hour. Once a developer creates a product in KiezelPay, they can download a pre-built watchface that includes the KiezelPay integration that has been configured for the product they just setup! ### Features Our KiezelPay platform currently offers you the following features: - **Trial usage** : You, as the developer, can choose if you want to allow users to test your app before purchasing by having a trial period. With a simple setting in your KiezelPay account, you can enable/disable trials, and define how long they last. After the trial is expired, the user will be required to purchase to continue. - **Discounts** : Want to reward that user that helped you hunt down a hard to find bug with a free license or have a holiday specific pricing? No problem! Our system allows you to create all kinds of discounts for every situation: percentage based or fixed amount, one time valid or during a specific period in time, with code or for anyone that purchases, etc. - **Dashboard** :​ Your KiezelPay account will show you all kinds of information about your apps. You can see a nice graph with your day­-to-­day income, easily see how much money you have made in total, how much you will receive at the next payout, and when that payout will happen. - **Purchase History** : We made it easy to find any of your customers to reach out to them, see information about when they purchased the app, and what may have gone wrong during failed purchases. - **Test mode** : By changing a single define in the KiezelPay library header file, you can run your app in test mode, allowing you to test the complete KiezelPay purchase process without having to pay for real. ## The Future! Kiezel is always looking to improve KiezelPay and would love any feedback you may have. We plan to improve KiezelPay in the coming weeks by adding the following: - Acceptance of Credit/Debit Card transactions - New dashboard analytics and graphs - Developer initiated refunds - Support for selling complete packages of apps/faces instead of only single apps/faces - In­-app purchases - And even more! KiezelPay is officially now live and open to all developers to sign up for free. To sign up visit our site at [KiezelPay.com](https://kiezelpay.com), and join the [KiezelPay Discord server](https://discord.gg/zbqrxh8). We hope you enjoy what we’ve created! Kristof Verpoorten & Jaron Pulver KiezelPay LLC * * * --- # Developer Relations Team Update Source: https://developer.repebble.com/blog/2016/03/29/developer-relations-team-update/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 29 March 2016 - [Cat Haines](https://developer.repebble.com/blog/authors/cat/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2016/03/29/developer-relations-team-update.md#comments) If you have been following Pebble in the news, you have likely seen that there have been some recent changes to the Pebble Team. We wanted to take a moment to acknowledge the #awesome work that has gone into getting our developer program to where it is today, and introduce you to the (somewhat) new faces of Pebble's Developer Relations team! ![2015 Developer Retreat](https://developer.repebble.com/assets/images/blog/developer-relations-team-update/retreat.jpg) The 2015 Developer Retreat - Bringing the Pebble Dev Team together with the Pebble Community! The Developer Relations team is responsible for overseeing Pebble's developer program, managing documentation and events, listening to and advocating for our community's needs, and creating a safe, welcoming and empowered community of Pebble developers. It's a big job - and we are incredibly lucky and grateful to be able to continue the work of the brilliant and compassionate team that came before us! And now to introduce you to the (somewhat) new faces of Developer Relation: Cat and Jon! ![Cat and Jon](https://developer.repebble.com/assets/images/blog/developer-relations-team-update/faces.png) Cat & Jon (with special guest appearance by Jess the dog) Cat Haines joined Pebble in October 2015, and has been helping with a lot of the behind the scenes work for Pebble's Developer Relations team. Cat enjoys tinkering with hardware and JavaScript (so naturally she's excited about [Rocky](https://github.com/pebble/rockyjs)), crafting, and trying her best to keep a variety of house plants alive. You can follow her antics on [Twitter](https://twitter.com/_cathaines), [GitHub](https://github.com/cat-haines), and her [personal blog](http://cathaines.com). Jon Barlow joined Pebble in September 2015. He is a champion Kickstarter backer and has been a longstanding community member and forum moderator. You may remember him from such apps as [91 Dub](https://apps.getpebble.com/en_US/application/52b231c2b70e1c159500009b) and [The Day Today](https://apps.getpebble.com/en_US/application/55a4b474e6f909512b0000a5). Jon has spent many years volunteering and helping people within the Pebble community and is looking forward to continuing this role as his day job. Jon works for Pebble remotely from the UK and you can find him on [Twitter](https://twitter.com/orviwan) and [GitHub](https://github.com/orviwan). We have some exciting updates in the works - including introductions to some of the other well know (and loved) developer facing teams at Pebble. We can't wait to share what's coming up - so stay tuned, and if you have any questions, you can always find Cat (@cat) or Jon (@orviwan) on Pebble's Slack channel. Happy Hacking! * * * --- # Don't Panic - We're Here to Guide You Source: https://developer.repebble.com/blog/2016/03/09/Dont-Panic-We-Are-Here-To-Guide-You/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 09 March 2016 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/03/09/Dont-Panic-We-Are-Here-To-Guide-You.md#comments) ![](https://developer.repebble.com/assets/images/blog/dontpanic.png) New developers typically begin their journey by following our tutorials, and then moving on to projects of their own. The guides section of our documentation is intended to help developers go beyond the basics - to help understand some of the more complex APIs and concepts required to develop a Pebble app. Ultimately, we want developers to feel comfortable enough with our platform that the reference documentation can be their primary source of information. We’ve known for a while that our guides section was a bit of a weak link (sorry for the inconvenience!) - and to that end, we’ve spent a lot of time thinking about, and writing, a new set of guides that should help to better guide you along your path to becoming an awesome Pebble developer! ## What Did We Do? - We’ve restructured the [Table of Contents](https://developer.repebble.com/guides/toc.md) and how we organize guides to make it easier to find the information you’re looking for. - We’ve substantially improved our [_Communication_](https://developer.repebble.com/guides/communication.md) guides (i.e. AppMessages, Datalogging, etc), which we’ve found to be one of the concepts new developers struggle with the most. - We’ve added a new [_Best Practices_](https://developer.repebble.com/guides/best-practices.md) section to help you take your apps to the next level by introducing concepts such as creating low power apps and watchfaces, and suggestions on how to organize larger projects. - We’ve created guides around [_Appstore Publishing_](https://developer.repebble.com/guides/appstore-publishing.md) to help you get your hard work into the hands (onto the wrists?) of others. - We’ve added new guides for [_Pebble Draw Commands_](https://developer.repebble.com/guides/graphics-and-animations/vector-graphics.md), and more areas of Pebble development. ## Why Did We Do It? Some of the older guides, as well as the overall structure of this section of our site, have been around for a long time. While seasoned developers are likely familiar with where they can find various tidbits of buried information, more recent additions to the Pebble developer family often struggle to find the information they need. But we haven’t just updated how we organize our guides - we’ve also taken this opportunity to update some of the best practices (and language) used throughout the guides. We’ve learned a lot over the past few years, and it’s safe to say we’re all better at this than we were when the guides were first published. ## How Did We Do It? The old set of categories was introduced in 2014. Since then we have seen the addition of new content for SDK 3.x, Pebble timeline, Design and Interaction, Smartstraps, and other smaller additions. When we started this project, we had a total of 79 guides in 20 different nested categories. After brainstorming what information should be kept as is, improved, added, and dropped, we ended up with a set of 64 guides across 13 categories. After the drafts were agreed upon, we set to work with the task of writing the actual content, using a combination of the existing content and an appreciation of the most common questions and problems raised by our developer community. With all of the new content in place, the final task was to shore up the improved version of the developer site. Links were checked, and redirects were created for all of the old guides - so if you have any favourite guides that you’ve bookmarked they should automagically take you to the appropriate section in the new guides. In total, the project resulted in 16,467 lines added and 18,386 lines removed across 370 files. Wow! ## What Are We Doing Next? If we’ve done our jobs, the new guides shouldn’t need any major updates for the foreseeable future, and the new structure should make it easier to extend, improve, and add to our guides when new features come along. Looking beyond the guides section, we have a lot of ideas (and work planned) to further improve the Pebble Developer site, and we’re excited to share it with you as soon as it’s ready. As always, keep the feedback and bug reports coming! Hopefully nothing is missing or broken, but if it is be sure to tweet [@PebbleDev](https://twitter.com/PebbleDev) on Twitter or find me in our Discord group. * * * --- # Take the Pebble Health API in your Stride Source: https://developer.repebble.com/blog/2016/03/07/Take-the-Pebble-Health-API-in-your-Stride/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 07 March 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/03/07/Take-the-Pebble-Health-API-in-your-Stride.md#comments) I've been desperate to write a blog post about the Pebble [HealthService API](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md) since it's initial release last month, and now I can finally share its awesomeness with you. I'm also going to show you how you can use this exciting new API to build an ultra-cool clone of the [Stride watchface](https://apps.repebble.com/en_US/application/56b15c5c9c4b20ed5300006c) by Pebble. ## Background Back in December 2015, Pebble Health was launched and had a massive uptake from the existing Pebble userbase. For some reason, I foolishly thought health was purely aimed at fitness fanatics and I completely underestimated its potential uses and benefits to non-athletes like me. To give you a bit of my background, I'm an overweight father of two, I don't get much sleep and probably spend an unhealthy amount time in front of a computer screen. I enabled Pebble Health, and as time progressed, I began to see how little exercise and how little sleep I actually get. Once you have started visualising health information, you can begin to see patterns and get an understanding of what 'good' days and 'bad' days look like. You can then make incremental changes and see how that affects your mood and quality of life, though to be completely honest, I haven't changed my bad habits just yet. I haven't suddenly started running every day, nor have I magically started getting \>5 hours sleep each night, but I now have a wealth of data available to help shape my future lifestyle decisions. ## What health data is available? The HealthService API exposes various [`HealthMetric`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthMetric) values which relate to the user being physically active, or sleeping. - `HealthMetricStepCount` - The number of steps counted. - `HealthMetricActiveSeconds` - The number of seconds spent active (i.e. not resting). - `HealthMetricWalkedDistanceMeters` - The distance walked, in meters. - `HealthMetricSleepSeconds` - The number of seconds spent sleeping. - `HealthMetricSleepRestfulSeconds` - The number of sleep seconds in the 'restful' or deep sleep state. - `HealthMetricActiveKCalories` - The number of kcal (Calories) burned while active. - `HealthMetricRestingKCalories` - The number of kcal (Calories) burned while resting due to resting metabolism. ## Querying the health data There are a few important things you need to do before you actually attempt to access the data from the HealthService. 1. Indicate that your watchapp will be accessing Health data by adding `health` to the `capabilities` section of the `appinfo.json`, or checking the ‘Uses Health’ option in your project settings in CloudPebble. This is used by the Pebble Time mobile app to inform users who are installing your watchapp that you're going to be accessing their health data. If you don’t do this and attempt to access a health api, your app will be terminated. 2. Check if the user has enabled Pebble Health in the Pebble Health app settings and that there is any health data available. This is done by checking the [`HealthServiceAccessibilityMask`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#HealthServiceAccessibilityMask) using [`health_service_metric_accessible()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/HealthService.md#health_service_metric_accessible). Here is a basic example of the steps you should take before attempting to access health data: ``` // Between midnight (the start time) and now (the end time)time\_t start = time\_start\_of\_today();time\_t end = time(NULL);// Check step count data is actually availablebool any\_data\_available = HealthServiceAccessibilityMaskAvailable & health\_service\_metric\_accessible(HealthMetricStepCount, start, end); ``` ## Building a Stride clone ![stride](https://developer.repebble.com/assets/images/blog/2016-03-07-image03.png) Stride by Pebble The Stride watchface displays the current time and your current step count for today. Around the edge of the screen it displays a bar which is your progress towards your average daily step count. The yellow line shows your daily average step count for the current time of day. So as the day progresses, the yellow line moves towards the end of your total step goal. If you manage to get ahead of the yellow line, you’re on track to beat your daily step count and to highlight this, all of the blue elements turn green. We're going to break down this project into several steps, so you can easily see what's going on. I'm going to cheat slightly to keep the examples short. Specifically we'll use emojis instead of the shoe icon, we won't be displaying the am/pm indicator and we’ll only be drawing circular progress bars on all platforms. ## Step 1 - Building the basic watchface ![strider-step1](https://developer.repebble.com/assets/images/blog/2016-03-07-image00.png) We'll start by creating a fairly typical structure of a watchface. We initialise a new `Window`, add a `TextLayer` to display the time, then set up a tick handler to update the time every minute. [View the source code](https://github.com/pebble-examples/strider-watchface/tree/step1/src/Strider.c) ## Step 2 - Add the step count & icon ![strider-step2](https://developer.repebble.com/assets/images/blog/2016-03-07-image07.png) Now we need to add a new `TextLayer` to display our step count and emoji. We're going to need to query the HealthService API to retrieve: - The current step count for today. - The target step goal amount. - The average step count for the current time of day. The step goal only needs to be updated once per day and we're going to use the [`HealthEventMovementUpdate`](https://developer.repebble.com/guides/pebble-apps/sensors/health/#subscribing-to-healthservice-events) event to trigger an update for the other data. This event will probably trigger multiple times per minute, so if you’re looking to conserve energy, you could manually update the data. Stride also changes the color of the text and icon if the user has exceeded their average step count, so we'll do that too. [View the source code](https://github.com/pebble-examples/strider-watchface/tree/step2/src/Strider.c) ## Step 3 - Add the progress indicators and progress bar ![strider-step3](https://developer.repebble.com/assets/images/blog/2016-03-07-image04.png) We’re going to create 2 new layers, one for the grey progress indicator dots and the other for the progress bar. To simplify the example, we’re just dealing with a circular indicator. If you want to see how Stride actually draws rectangularly, checkout the [health-watchface on Github](https://github.com/pebble-examples/health-watchface). The dots layer update procedure calculates the coordinates for each point using [`gpoint_from_polar()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#gpoint_from_polar) and then draws a circle at that point, for each dot. The progress layer update procedure uses [`graphics_fill_radial()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_radial) which can fill a circle from a start and end angle, we’re using a narrow inset thickness so that the circle is just drawn as a ring. We also need to redraw the progress layer when the step count changes, but all we need to do is mark the layer dirty. [View the source code](https://github.com/pebble-examples/strider-watchface/tree/step3/src/Strider.c) ## Step 4 - Add the average step indicator ![strider-step4](https://developer.repebble.com/assets/images/blog/2016-03-07-image01.png) The final thing we’re going to add is an indicator to show your average daily steps for this time of day. We’ve already calculated the average, and we’re going to use [`graphics_fill_radial()`](https://developer.repebble.com/docs/c/Graphics/Drawing_Primitives.md#graphics_fill_radial) again but this time it’s just to draw a yellow line. We’ll need to add another new layer and update procedure to handle the drawing of the line. We also need to redraw the new layer when the step average value changes, but again, all we need to do is mark the layer dirty. [View the complete source code](https://github.com/pebble-examples/strider-watchface) ## The finished product ![strider-step2b](https://developer.repebble.com/assets/images/blog/2016-03-07-image05.jpg) Strider watchface ## Final thoughts I’ve really only scratched the surface of the HealthService API, but hopefully you’re now sufficiently excited to build something awesome! We’d really love to see how you use it, and If you create a health enabled watchapp or watchface, don’t forget to let us know on [Twitter](http://twitter.com/pebbledev) or on Discord! * * * --- # Introducing App Debugging Source: https://developer.repebble.com/blog/2016/02/29/introducing-app-debugging/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 29 February 2016 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/02/29/introducing-app-debugging.md#comments) Happy leap day! Today is a once-every-four-years day of bizarre date-related bugs, and thus an opportune moment for us to introduce our latest developer feature: app debugging in the emulator! This gives you a powerful new way to hunt down errors in your apps. A new command is available in our preview release of pebble tool 4.2 and SDK 3.10: `pebble gdb`. Running this command from a project directory on an emulator with your app installed will pause the emulator and attach a gdb debugger instance to it: ``` katharine@scootaloo ~/p/pebble-caltrain (master)> pebble gdb --emulator basalt Reading symbols from /Users/katharine/Library/Application Support/Pebble SDK/SDKs/3.10-beta4/sdk-core/pebble/basalt/qemu/basalt_sdk_debug.elf...(no debugging symbols found)...done. Remote debugging using :49229 0x0801cd8c in ?? () add symbol table from file "/Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf" at .text_addr = 0x200200a8 .data_addr = 0x20023968 .bss_addr = 0x200239b8 Reading symbols from /Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf...done. Breakpoint 1 at 0x804aebc Press ctrl-D or type 'quit' to exit. Try `pebble gdb --help` for a short cheat sheet. Note that the emulator does not yet crash on memory access violations. (gdb) ``` Do note that once we pause execution by launching gdb, emulator buttons and any pebble tool command that interacts with the emulator won't work until we continue execution (using `continue` or `c`) or quit gdb. Once we're here, we can add some breakpoints to the app in interesting places. Here we are debugging my [Caltrain app](https://github.com/Katharine/pebble-caltrain). Let's say I think there's a bug in the search for the next train: I probably want to poke around in [`find_next_train`](https://github.com/Katharine/pebble-caltrain/blob/f4983c748429127a8af85911cb123bd8c3bacb73/src/planning.c#L4). We can run `b find_next_train` to add a breakpoint there: ``` (gdb) b find_next_train Breakpoint 2 at 0x20020f1e: file ../src/planning.c, line 5. (gdb) ``` Now we can use the `c` or `continue` command to set my app running again, until it stops at `find_next_train`: ``` (gdb) c Continuing. ``` The app runs as usual until we open a station, which causes it to look up a train, where it hits the breakpoint and pauses the app so we can inspect it: ``` Breakpoint 2, find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738) at ../src/planning.c:5 5 TrainTime *best[2] = {NULL, NULL}; (gdb) ``` Now we can see how we got here using the `backtrace` or `bt` command: ``` (gdb) bt #0 find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738) at ../src/planning.c:7 #1 0x200211b2 in next_train_at_station (station=13 '\r', northbound=0x20025a0c , southbound=0x20025a14 ) at ../src/planning.c:76 #2 0x200215c8 in prv_update_times () at ../src/stop_info.c:106 #3 0x200216f8 in show_stop_info (stop_id=13 '\r') at ../src/stop_info.c:174 #4 0x200219f0 in prv_handle_menu_click (menu_layer=0x2002fe3c, cell_index=0x2002ff0c, context=0x2002fe3c) at ../src/stop_list.c:57 #5 0x0805cb1c in ?? () #6 0x0805a962 in ?? () #7 0x0801ebca in ?? () #8 0x0801e1fa in ?? () #9 0x200202d6 in main () at ../src/main.c:23 #10 0x080079de in ?? () #11 0x00000000 in ?? () ``` The `??` entries are inside the pebble firmware; the rest are in the Caltrain app. We can step forward a few times to get to an interesting point using the `step` or `s` command: ``` (gdb) s 7 const time_t timestamp = time(NULL); (gdb) s 8 const uint16_t minute = current_minute(); (gdb) s current_minute () at ../src/model.c:183 183 const time_t timestamp = time(NULL); (gdb) ``` Now we've stepped into another function, `current_minute`. Let's say we're confident in the implementation of this (maybe we wrote unit tests), so we can jump back up to `find_next_train` using the `finish` command: ``` (gdb) finish Run till exit from #0 current_minute () at ../src/model.c:183 0x20020f38 in find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738) at ../src/planning.c:8 8 const uint16_t minute = current_minute(); Value returned is $2 = 738 (gdb) ``` When we step to the next line, we see it has a similar `current_day` that we don't need to inspect closely, so we jump over it using the `next` or `n` command: ``` (gdb) s 9 const uint8_t day = current_day(); (gdb) n 11 for(int i = 0; i < count; ++i) { (gdb) ``` Now we can double check our current state by using `info locals` to look at all our local variables, and `info args` to look at what was originally passed in: ``` (gdb) info locals i = 184 best = {0x0 < __pbl_app_info>, 0x0 <__ pbl_app_info>} timestamp = 1456776942 minute = 738 day = 1 '\001' (gdb) info args count = 184 times = 0x2002f414 nb = 0x2001873c sb = 0x20018738 (gdb) ``` `timestamp`, `minute` and `day` all have the values they gained from our last few function calls. `best` is still a pair of NULL pointers, and `i` hasn't been assigned yet, so its value is garbage. Once we step another line it'll be filled in, which we can check using the `print` or `p` command: ``` (gdb) s 12 TrainTime *train_time = &times[i]; (gdb) p i $3 = 0 ``` Now let's step forward and have it fill in `train_time`, and see what we get: ``` (gdb) s 14 trip_get(train_time->trip, &trip); (gdb) p train_time $4 = (TrainTime *) 0x2002f414 (gdb) ``` This is unenlightening — it's just the same pointer as `times`, which is what we expect when referencing `×[0]`. Fortunately, `print`/`p` will evaluate arbitrary expressions, so we can dereference the pointer to see what it actually points at: ``` (gdb) p *train_time $5 = {trip = 189, time = 309, stop = 13 '\r', sequence = 10 '\n'} (gdb) ``` Better! It might be more interesting to just print that out for each loop iteration, so let's set a breakpoint here and have it print `*train_time` and continue: ``` (gdb) b Breakpoint 3 at 0x20020f62: file ../src/planning.c, line 14. (gdb) commands Type commands for breakpoint(s) 3, one per line. End with a line saying just "end". >p *train_time >c >end (gdb) c Continuing. Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738) at ../src/planning.c:14 14 trip_get(train_time->trip, &trip); $6 = {trip = 209, time = 344, stop = 13 '\r', sequence = 11 '\v'} Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738) at ../src/planning.c:14 14 trip_get(train_time->trip, &trip); $7 = {trip = 199, time = 345, stop = 13 '\r', sequence = 13 '\r'} ``` …and so on. A bit noisy, so let's remove that breakpoint now: ``` (gdb) delete 3 (gdb) ``` Finally, let's have our program continue on its way by running `c` again: ``` (gdb) c Continuing ``` When we want to get out of gdb we'll need our `(gdb)` prompt back, so press ctrl-C to pause the app again: ``` ^C Program received signal SIGINT, Interrupt. 0x08007072 in ?? () (gdb) ``` This will most likely pause execution inside some firmware code, as we did when we initially launched gdb. We can now do anything we've done before, but we're just going to quit: ``` (gdb) quit A debugging session is active. Inferior 1 [Remote target] will be killed. Quit anyway? (y or n) y katharine@scootaloo ~/p/pebble-caltrain (master)> ``` Hopefully this has given you some ideas as to how you might be able to use gdb to debug your own apps. If you'd like to know more about gdb, [here is a Q&A-style tutorial](http://www.unknownroad.com/rtfm/gdbtut/) that will answer many questions you might have. Good luck and happy debugging! * * * --- # Guest Post - My Experience with Paid Apps Source: https://developer.repebble.com/blog/2016/02/26/mathew-reiss-paid-apps/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 26 February 2016 - [Cat Haines](https://developer.repebble.com/blog/authors/cat/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2016/02/26/mathew-reiss-paid-apps.md#comments) _This is a guest post by Mathew Reiss, developer of countless Pebble apps including [Snowy](https://apps.getpebble.com/en_US/application/561960c8a1dd2652af00000d) and [Yahoo! Fantasy Football](https://apps.getpebble.com/applications/55e74527646181ef07000047)._ My goal with writing apps for Pebble has never been to make tons of money. Despite [Snowy's success](https://twitter.com/KiezelPay/status/680484731079659520), I'm not going to go quitting my job and moving to an exotic island any time soon. However, it has been absolutely awesome making some money off my hobby. Most importantly, having paid users validates my efforts as a developer and proves that what I'm building is not just something that users want, but something that they're willing to pay for. ![Steps to Profit](https://developer.repebble.com/assets/images/blog/mathewreiss_paidapps/profit.png) It's that easy! Snowy started out as a basic voice calculator, with all parsing and processing taking place in C. However, once I moved the parsing code into JavaScript, I realized that there were nearly endless possibilities. So Snowy, the personal assistant for Pebble Time, was born. I distinctly remember telling my wife that night that I felt I'd finally made something people might be willing to pay for... ## Getting Paid, Part 1 - Setting Up Payments It turns out, I was correct. But at the time, I faced a serious question. _How_ do I get people to pay for my app? There were two problems: one technical, and one systemic. The technical problem was how to accept payments, the established way of doing this for Pebble apps is typically to write a companion app for iOS and/or Android. But there was a tiny issue: I don't know how to write iOS and/or Android apps, and even if I did, Apple and Google take a big cut of your potential revenue stream. This is where [KiezelPay](https://kiezelpay.com/) comes into the picture. For those of you unfamiliar with the platform, KiezelPay is a third-party library that you integrate into your code. It checks whether the user has purchased a license, and if they haven’t, a notification pops up on the watch with a 5 digit code and prompts the user to visit the KiezelPay website. There, the user enters the code, which launches a payment flow through PayPal. It’s easy to implement, and they take less of a cut than Google or Apple. ![Batman and Robin](https://developer.repebble.com/assets/images/blog/mathewreiss_paidapps/batman.png) Holy Profitable Pebble Programming, Batman! The library consists of two C files and some JavaScript code. Initiating the license check and payment prompt is as easy as adding a single line of code to your `init()` function: ``` kiezelpay\_init(); ``` You can easily customize the user experience if you want, to show custom payment prompts (which Snowy does), or to allow a trial period (which Snowy does not - more on that shortly). ## Getting Paid, Part 2 - Getting Users to Pay Now that the technical side was taken care of, I still needed to address the systemic issue: almost all apps in the Pebble appstore are free. This creates a very unusual set of expectations for an app “marketplace” - namely, that any new app is _expected to be_ free since almost all other apps _are_ free. KiezelPay does offer the option for a free trial period; however, I felt confident that Snowy wouldn’t need one. The success of the app would speak for itself. Additionally, post-launch, I released a short [demo video](https://youtu.be/-XVLs-WZ_yo). This satisfied many users who had complained about not having a trial. They (understandably) wanted to see the app in action before buying a license. The time I saved not integrating the trial was spent creating marketing material that would support the app. This material consisted of a simple [website](http://www.mydogsnowy.com) where users could look up information on how to use Snowy and see answers to frequently asked questions. Not only did this spare my inbox of most (but not all) basic functionality questions, but I believe it added a legitimacy to the product that wouldn’t have been there if I just had published a PBW and hoped for the best. ![MyDogSnowy.com Homepage Banner](https://developer.repebble.com/assets/images/blog/mathewreiss_paidapps/snowy_banner.png) MyDogSnowy.com Homepage Banner The next step was to promote Snowy in channels where I expected solid conversion. This included the [Pebble Forums](https://forums.getpebble.com/discussion/30378/app-personal-assistant-meet-snowy-the-worlds-first-personal-assistant-for-pebble-time/p1), [Twitter](https://twitter.com/Idiot_Economist/status/679314463103836160), and [Reddit](https://www.reddit.com/r/pebble/comments/3xu8zj/meet_snowy_the_worlds_first_personal_assistant/) (r/pebble). The response on Reddit was the most significant, early adopters began sharing their positive reviews, and soon more casual Pebblers were downloading Snowy. And there were definitely some people opposed, not to Snowy in particular, but to paid apps in general. Some demanded a free trial. But the opinions of those in the increasingly small minority who insisted on a trial or refused to pay for a Pebble app mattered less and less as more and more users purchased licenses. ## Key Takeaways So what are my biggest takeaways from this experience? First and foremost, be thorough in your documentation. Whether that’s a highly-detailed appstore description or a simple WordPress site, you want users to be comfortable giving you their money in exchange for your work. It should also reduce support emails, which leads me to my second point. Check your support emails frequently after each release. I try to respond in a timely fashion to emails for all of my apps, but there’s an increased sense of urgency if someone paid for one. Aim for a high level of professionalism, even when responding to angry users or those who want a refund. It'll be better for your long term credibility and success. Also important, be highly visible in your pricing. This may seem counter- intuitive, but if users think you're trying to hide the price, there will be strong backlash. Be up front and honest about the price, and most will accept that. ![Snowy appstore Banner](https://developer.repebble.com/assets/images/blog/mathewreiss_paidapps/snowy_appstore.png) Banner design (courtesy of Juan Sanchez) makes the price look like an integrated part of the Pebble appstore user interface. However, some users very explicitly won’t accept paid apps, and you need to accept that. To paraphrase Abraham Lincoln, you can’t please all your users all the time. There will be users that actively hate some detail or feature and they won’t hesitate to let you know about it via that convenient “Email Developer for Support” email link in the appstore. Sometimes, you’ll get valuable feedback that way. More often than not, you’ll just need to remind yourself that for every angry email, there are dozens, if not hundreds of perfectly happy people using and loving your watchface or app every day. Finally, my mantra for paid apps is this: “Charge money only for apps worth paying for.” Asking a user to pay real money for an app is asking them to have faith in the quality of your work. Users will lose faith in paid apps as a whole if the Pebble appstore is suddenly flooded with low-quality paid apps. The flip side of this is; if you _do_ have an app that you believe is “worth paying for”, you’re going to stand out in the crowd. Putting a pricetag on your app will indicate to users that you believe it's worth something, and they should too. As for me, I’m content to continue working on supporting and [updating Snowy](https://www.reddit.com/r/pebble/comments/43owzc/snowy_11_now_available_in_pebble_appstore/) at this point. However, I’m excited to see more widespread adoption of KiezelPay, and a new generation of paid apps in the Pebble appstore. _Note: KiezelPay is still in beta, but is accepting applications for early access now. To apply, sign up at [https://www.kiezelpay.com](https://www.kiezelpay.com)._ * * * --- # JavaScript Libraries for C Developers (pt. 1) Source: https://developer.repebble.com/blog/2016/02/19/JavaScript-Libraries-for-C-Developers-pt-1/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 19 February 2016 - [Cat Haines](https://developer.repebble.com/blog/authors/cat/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2016/02/19/JavaScript-Libraries-for-C-Developers-pt-1.md#comments) One of the exciting changes introduced in [SDK 3.9](https://developer.repebble.com/sdk/changelogs/3.9.md) was support for including [Multiple JavaScript Files](https://developer.repebble.com/blog/2016/01/29/Multiple-JavaScript-Files.md) to your projects. While this feature doesn’t allow you to run any JavaScript you previously couldn’t, it makes organizing your PebbleKit JS code a heck of a lot easier. In this blog post, we'll look at how to refactor some exisiting JavaScript 'library code' into an actual module for PebbleKit JS, making it simpler to use and share! ## OWM-Weather A few months ago we published a very simple 'library' for working with the [Open Weather Map](http://openweathermap.org/) API. The JavaScript side of the code takes care of grabbing the user’s location with the built in [geolocation.getCurrentPosition](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) API, then fetches weather information for the returned position. The initial version of the code we’re working with can be found [here](https://github.com/pebble-hacks/owm-weather/tree/8c4f770e591fe5eff65209ebb6fe6ef23152d81a). ## Creating JavaScript Classes > **Note:** While JavaScript (with ECMAS5) does not allow for true "Classes", JavaScript does a support a mechanism that allows you write Class-like code. For convience, we'll be refering to these objects as 'classes' throughout this post. The first thing we want to do is wrap our library code in an Object constructor function, which is what allows us to treat `OWMWeather` as a class, and rewrite our functions so they're properties belonging to `this` (an instantiated version of the object). If you're interested in diving a bit deeper into how this works, take a look at Pivotal's great blog post about [JavaScript constructors, prototypes, and the 'new' keyword](https://blog.pivotal.io/labs/labs/javascript-constructors-prototypes-and-the-new-keyword). ``` var OWMWeather = function() {this.owmWeatherAPIKey = '';this.owmWeatherXHR = function(url, type, callback) {//...};//...}; ``` We’ll also need to change our JS application code to construct a new `OWMWeather` object, and change our reference of `appMessageHandler()` in the in the `Pebble.addEventListener('appmessage', ...)` callback to `owmWeather.appMessageHandler()`. ``` var owmWeather = new OWMWeather();Pebble.addEventListener('ready', function(e) {console.log('PebbleKit JS ready!');});Pebble.addEventListener('appmessage', function(e) {console.log('appmessage: ' + JSON.stringify(e.payload));owmWeather.appMessageHandler(e);}); ``` ## Using the .bind() function If we try to run our code at this point in time, we're going to run into a slew of errors because we've changed the scope of the functions, but we haven't updated the places where we call them. We need to change `owmWeatherFunctionName()` to `this.owmWeatherFunctionName()` (same goes for the API key). Here's what the new `owmWeatherLocationSuccess` method looks like (changes are indicated with in-line comments): ``` this.owmWeatherLocationSuccess = function(pos) {// Change owmWeatherAPIKey to this.owmWeatherAPIKeyvar url = 'http://api.openweathermap.org/data/2.5/weather?' +'lat=' + pos.coords.latitude + '&lon=' + pos.coords.longitude +'&appid=' + this.owmWeatherAPIKey;console.log('owm-weather: Location success. Contacting OpenWeatherMap.org..');this.owmWeatherXHR(url, 'GET', function(responseText) {console.log('owm-weather: Got API response!');if(responseText.length \> 100) {// Change owmWeatherSendToPebble(..) to this.owmWeatherSendToPebble(..)this.owmWeatherSendToPebble(JSON.parse(responseText));} else {console.log('owm-weather: API response was bad. Wrong API key?');Pebble.sendAppMessage({ 'OWMWeatherAppMessageKeyBadKey': 1 });}// Add .bind(this) to the closing brace of the callback}.bind(this));}; ``` An observant developer might notice that along with changing `owmWeatherXHR` to `this.owmWeatherXHR`, we've also added `.bind(this)` to the end of the callback function. We're not going to dive too deeply into how `this` and `bind` works (that's a whole blog post on its own), but what I will say is that the `bind` method can be thought of as modifying a function so that it will, when invoked, have its `this` keyword set to the provided parameter. > If you want to learn more about JavaScript Objects, scope, and `bind`, I will encourage you to read [You Don't Know JS: _this_ & Object Prototypes](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/README.md#you-dont-know-js-this--object-prototypes). We'll want use `bind(this)` (where `this` will be the instance of the OWMWeather class) whenever we're using an OWMWeather method as a callback from within the OWMWeather code. ``` navigator.geolocation.getCurrentPosition(this.owmWeatherLocationSuccess.bind(this),this.owmWeatherLocationError.bind(this), {timeout: 15000,maximumAge: 60000}); ``` At this point, our code should look like [this](https://github.com/pebble-hacks/owm-weather/blob/8c9ab77a66d5c38719acbdfce939fbfac6d12235/owm_weather/owm_weather.js). ## Using module.exports The last thing we want to do to make this into a handy module is extract the code into its own file (`/src/js/lib/owm_weather.js`), and use the [module.exports](http://www.sitepoint.com/understanding-module-exports-exports-node-js/) API to export our OWMWeather class. ``` var OWMWeather = new function() {//...};module.exports = OWMWeather; ``` In order to use this in our PebbleKit JS application, we need to do a couple things.. If you're using CloudPebble: - Change **JS Handling** to _CommonJS-style_ in the project settings If you're using the SDK: - Update our `appinfo.json` to include `'enableMultiJS': true` if it isn't already there - Rename `src/js/pebble-js-app.js` to `src/js/app.js` Once we've made these changes to our files, we're ready to include OWMWeather in our `app.js` file with the [require API](http://www.sitepoint.com/understanding-module-exports-exports-node-js/#importing-a-module). ``` var OWMWeather = require('./lib/owm\_weather.js');var owmWeather = new OWMWeather();Pebble.addEventListener('ready', function(e) {console.log('PebbleKit JS ready!');});Pebble.addEventListener('appmessage', function(e) {console.log('appmessage: ' + JSON.stringify(e.payload));owmWeather.appMessageHandler(e);}); ``` At this point, our code should something look like [this](https://github.com/pebble-hacks/owm-weather/tree/597c717627c281b56ff303ca94f35789002a969e). ## Refactoring Variable and Function Names Since we’ve moved all of the OWMWeather code into a class, we can safely remove the `owmWeather` prefixes on all of our methods and properties. While we’re at it, we're also going to rename functions and properties that are intended to be private to begin with an `_`, which is fairly common practice: ``` var OWMWeather = function() {this.\_apiKey = '';this.\_xhrWrapper = function(url, type, callback) {//...};//...}; ``` ## What's next.. And that's it - we've successfuly refactored our code into a module that should be easier for developers to use and share (which is exactly what we want). You can view the full source code for this blog post [here](https://github.com/pebble-hacks/owm-weather/tree/657da669c3d9309a956f655c65263b8dc06cec1f). In the next blog post, we'll take this library a step further and look at how you can abstract away the need for `appKeys` in your `appinfo.json` file to make working with the library _even easier_... Until then, happy hacking! * * * --- # Hands-on with the RePhone smartstrap kit for Pebble Time Source: https://developer.repebble.com/blog/2016/02/09/Hands-on-with-the-RePhone-smartstrap-kit-for-Pebble-Time/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 09 February 2016 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/02/09/Hands-on-with-the-RePhone-smartstrap-kit-for-Pebble-Time.md#comments) For those that don’t already know, the RePhone smartstrap kit was part of a highly successful [Kickstarter campaign](https://www.kickstarter.com/projects/seeed/rephone-kit-worlds-first-open-source-and-modular-p) by [Seeed Studio](http://www.seeed.cc/) to create the world’s first open source and modular smartphone. Part of their campaign was to create a smartstrap kit for Pebble Time that integrates with RePhone modules such as GPS, NFC, and external batteries. This is the first commercial smartstrap project I’ve managed to lay my hands on, and it’s awesome! The box contains everything you need to get started: - Xadow GPS v2 module - Xadow NFC v2 module - Xadow Battery 100mAh module - A Pebble Time adaptor - A 22mm watch strap with mounting holes - Ribbon cables, Mounting rivets and silicone casings - Instruction booklet ![rephone-smartstrap-kit-diagram](https://developer.repebble.com/assets/images/blog/rephone-smartstrap-kit-diagram.png) RePhone Strap Kit diagram courtesy of Seeed Studio Assembling the components was a reasonably straightforward process, although my chunky fingers and poor eyesight didn’t always make it quite as easy as it should have been. First you need to fix the adaptor to the back of your Pebble, then attach the watchstrap, making sure to feed the ribbon cables that extend from the adaptor through on top of the watch strap. Each module needs to be interconnected as per the instructions above. Seeed Studio provide an open source [Pebble watchapp](https://github.com/Seeed-Studio/Xadow_Strap/raw/master/build/pebbleapp_jack.pbw) which displays GPS coordinates, velocity, altitude, satellites and NFC (when in range). [This project](https://github.com/Seeed-Studio/Xadow_Strap/) can be compiled with the local [Pebble SDK](https://developer.repebble.com/sdk.md) or [CloudPebble](https://cloudpebble.repebble.com/). After getting everything hooked up and the watchapp installed, I was disappointed when the GPS didn’t initially appear to be working. I headed outside with my watch and stood in the street for a while, and pretty soon the number of visible satellites started to increase and then the GPS position was established. ![rephone-smartstrap-kit](https://developer.repebble.com/assets/images/blog/rephone-smartstrap-kit.jpg) It honestly felt like wearing some kind of futuristic time travel device with all of the wires and circuits exposed. I wouldn’t recommend walking into an airport like this, but I actually quite enjoyed the aesthetics. The next thing to test was the NFC module, and thankfully this was really simple. I held my NFC enabled phone against the NFC antenna and sure enough, the tag ID appeared. I also used it to successfully scan another NFC chip, but this one was embedded directly inside someone's hand. Apparently there’s some seriously dedicated next-level biohackers at Pebble! The battery module supplied with the kit is used to power the GPS and NFC modules, but you can also use it to charge the Pebble itself. I didn’t have time to perform any testing other than confirm that the Pebble was infact charging from the strap’s battery. The battery module is 100mAh, compared to the 150mAh battery in the Pebble Time, pretty impressive for something so small! There are other modules available for the RePhone kit, like GSM+BLE, audio, touch screen and LEDs, but it doesn’t look like the smartstrap kit is able to take advantage of those. However, I did stumble across a page on the [Seeed Studio wiki](http://www.seeedstudio.com/wiki/Xadow_Pebble_Time_Adapter), which explains how to build your own firmware for the smartstrap adaptor itself. So if you’re interested in taking things further, it’s great to know there are more options are available for the hardcore hardware hacker. The Pebble developer community have been eagerly hacking away with their kits, [ishotjr](https://twitter.com/ishjr) has published some unboxing photos on his [blog](http://ishotjr.com/seeed-rephone-smartstrap-for-pebble-time-unboxing/) and created a [useful wiki](https://github.com/ishotjr/rephone-smartstrap/wiki). If you want to get involved, you can find them in #smartstraps over on the Pebble Developer Discord. We've only just scratched the surface of what's possible with this smartstrap kit and we can't wait to see what the community produce. If you build a custom app using the RePhone Smartstrap Kit, we'd love to [hear about it](https://twitter.com/pebbledev). * * * --- # Multiple JavaScript Files Source: https://developer.repebble.com/blog/2016/01/29/Multiple-JavaScript-Files/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 29 January 2016 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2016/01/29/Multiple-JavaScript-Files.md#comments) In SDK 3.9 we will introduce a new feature to the Pebble SDK: the ability to cleanly use multiple JavaScript files in the PebbleKit JS app portion of your project. ~~SDK 3.9 is available now in beta! Check out our [beta instructions](https://developer.repebble.com/sdk/download/#testing-beta-sdks) to try it out.~~ **EDIT:** SDK 3.9 is now [publicly available](https://developer.repebble.com/sdk.md) - to update your SDK, run: `pebble sdk install latest` # How do I use this? First, if you are using the native SDK, you must make sure you have `"enableMultiJS": true` in your appinfo.json file. This defaults to false if omitted, but will be set to `true` in all projects created using SDK 3.9+ and Pebble Tool 4.1+. If you are using CloudPebble, ensure that "JS Handling" is set to "CommonJS-style" in your project's settings page. Having enabled it, PebbleKit JS now expects to have an "entry point" at `src/pkjs/index.js`. This is the code we will run when your app starts, and is equivalent to the native SDK's old `src/js/pebble-js-app.js`. Aside from a slightly less redundant name, the effective behavior is exactly the same as before. However, you can now add additional javascript files! These files will not be executed automatically. Instead, you can pull them in to your code using the `require` function, which returns a reference to the imported file. Since `require` returns something, there needs to be some way to provide something that it can usefully return. To do this, add properties to `module.exports` in the file to be required. For instance: **adder.js** ``` function addThings(a, b) {return a + b;}module.exports.addThings = addThings; ``` **index.js** ``` var adder = require('./adder');console.log("2 plus 2 is " + adder.addThings(2, 2)); ``` Running the app with this JavaScript would look like this: ``` katharine@scootaloo ~> pebble install --emulator basalt --logs Installing app... App install succeeded. [17:39:40] javascript> 2 plus 2 is 4 ``` That's about all there is to it: we handle this all transparently. # But…!? ### …how do I migrate my existing project? If you use the native SDK and your existing project has only one JavaScript file, just move it from `src/js/pebble-js-app.js` to `src/pkjs/index.js` and add `"enableMultiJS": true` to your appinfo.json file. Easy! If you use CloudPebble and have multiple JavaScript files, you first change "JS Handling" to "CommonJS-style" in your project's Settings page. If you don't have a JavaScript file called "index.js", but you do have some others, it will prompt you to rename a file. If you previously used multiple JavaScript files on CloudPebble, you will now need to make code changes. In particular, you will have to modify your code so that there is a single, clearly-defined entry point (`index.js`) and the other files use the module export system described above. That said, there is no need to do this now: the existing system will continue to work for the forseeable future. ### …will this work for users who haven't updated their mobile apps? Yes! This all happens entirely at build time; no updates to the user's phone software are required. Since it is part of SDK 3.9, they will need to be running firmware 3.9, as usual. ### …I use Pebble.js. What does this mean for me? Nothing! Pebble.js already does something very similar to this, and it will continue to do so. No changes are needed. ### …I built something like this myself. Do I have to use this? No! If you want to keep using your own custom system, you can set `enableMultiJS` to false or omit it entirely, and nothing will change. The system is strictly opt-in, and won't break anything you were doing if you leave it as-is. ### …I don't want my main JavaScript file to be called "index.js" We recommend that you use the default name for portability reasons. In particular, CloudPebble will _require_ that you use this name, and will fail to build a non-compliant project. However, if you would really like to change it, you can pass an alternate path as `js_entry_file` to `pbl_bundle` in your wscript file. * * * --- # Guest Post - Beating Traffic with Clouds and Pebbles Source: https://developer.repebble.com/blog/2016/01/26/Guest-Dries-Oeyen-Commute/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 26 January 2016 - [Katherine McAuliffe](https://developer.repebble.com/blog/authors/katherine/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2016/01/26/Guest-Dries-Oeyen-Commute.md#comments) _This is a guest post by Dries Oeyen, developer of the Pebble app [Commute](https://apps.getpebble.com/applications/564f706e86b141be60000081) and Rockstar of Belgium's Pebble Developer Meetup, [PebbleBelgium](http://www.meetup.com/PebbleBelgium/)._ Hello everyone. I'm [Dries Oeyen](http://nexworx.com) and I live in Belgium, a tiny country in Europe. In case you're wondering what Belgium is like: in the morning on weekdays, it looks roughly like this: ![Traffic jam in Brussels](https://developer.repebble.com/assets/images/blog/guestblogdries_belgium.jpg) Joking aside, traffic really is terrible here. Depending on a multitude of factors, my trip to work can take anywhere between 35 minutes and an hour. Firing up the Google Maps app every morning to check traffic conditions quickly became routine, but Maps is a big and kinda clunky app for this use-case. I only wanted to know when to hop in the car in order to arrive at work on time, and whether to take the highway or the backroad. More importantly: I wanted this information in a glanceable format, right at my fingertips. One day, as I was stuck in traffic once again, I looked at the Pebble on my wrist to check the time. And then it struck me. ## Build it... That night when I came home, I opened my code editor and I got to work. At that point, I had some limited experience with the Pebble SDK: I had written a basic Pebble.js app in 2014, which I then ported to the C SDK after taking a conveniently timed C class at school. However, this time I wanted to spice things up with a completely custom interface. Thankfully, Pebble's documentation is truly fantastic and somehow makes writing C a breeze (really!). I quickly got the hang of how to use Pebble's UI APIs, and before too long I shipped the first version of my new app. [Commute](https://apps.getpebble.com/applications/564f706e86b141be60000081) was born. > Strong start for Commute! Well over 100 users in just 12 hours. Happy with the adoption! :) [https://t.co/dYOvptAYDa](https://t.co/dYOvptAYDa) [pic.twitter.com/jMkbLiz09r](https://t.co/jMkbLiz09r) > > — Dries Oeyen (@DriesOeyen) [24 november 2015](https://twitter.com/DriesOeyen/status/669118468587933700) ## ... And they will come At this point, the app achieved my initial goal of providing instant, glanceable insight into traffic conditions. I posted a thread on Pebble's subreddit, [/r/pebble](https://reddit.com/r/pebble), that helped Commute attract a healthy 100 users within the first 12 hours of going live. I was obviously thrilled to see the app gain traction, but not just for the typical reasons one might expect. I had a second, more exciting goal for Commute: telling users when to leave for work in order to arrive right on time, by sending predictive timeline pins to their watches. Having a sizable group of early adopters from all over the world was a great opportunity for setting up a test group that would help me discover edge-cases as I developed the timeline algorithm. In fact, thanks to some planning ahead I already had the app collecting users' timeline tokens, so I could start deploying the feature to a test group transparently – no app update required. ## Integrating with Pebble Timeline I knew integrating Commute with Pebble Timeline represented a major challenge. Both Google App Engine and the Google Maps API come with free quota, but in the end they're very much finite resources. Keeping in mind the growing number of users, it became clear that I'd need to think about scaling. It was time to set up some constraints. My biggest priority was aggressively limiting the number of API calls to Google Maps, since you only get 2500 daily requests within the free tier. Doing the math, I quickly realized that the only way to pull this off was to use just a single Maps API request per user per trip. This represented a conundrum in terms of when exactly to perform said request: the later you fetch, the "fresher" the data, but you need to leave enough time to account for longer-than-usual traffic delays and the variable latency between a pin being sent and it appearing on the user's timeline. Using multiple requests, it would be possible to "hone in" on the ideal time to send a pin or to update it as fresher information becomes available, but using a single request per user per trip meant the algorithm had to be intelligent about when to perform said request. The solution I came up with is keeping track of the average home-to-work and work-to-home travel times for each user. Every time a pin is sent to a user, these statistics are updated, thus improving the timing of the next pin. This ensures increasingly well-timed pins with sufficiently fresh data, all while putting minimal strain on my App Engine resources. I also limited the load on my datastore while periodically checking who needs to receive a pin: instead of cycling through every user every time the cron job runs, I wrote a heavily optimized 68-line filtering query that only selects likely candidates. Yeah, I know. Cloud development is weird sometimes. Luckily, seeing the result in action proved that it was worth it. ![Commute timeline pin and reminder](https://developer.repebble.com/assets/images/blog/guestblogdries_pins.jpg) ## Testing and scaling timeline pins With all of the hard work done (or so I thought), I turned to my test group to see if everything worked as expected. As it turns out, that was a good call. What followed was a streak of about 30 bug fixing commits. Timezones are hard! I'll let these commit messages speak for themselves: - Fix scenario where pins could have the wrong date - Fix exception if arrival/departure is next day - Prevent division by zero while parsing directions - And my personal favorite: Don't send timeline pins during weekends Right before Christmas, Commute hit a big milestone: 1000 users. Meanwhile, I had been scaling up the test group as I squashed bugs. Since I was now sufficiently confident that the system worked as intended. I decided to celebrate by flipping the switch. > Commute for [@Pebble](https://twitter.com/Pebble) now has over 1000 users! To celebrate: timeline integration for all! 🎉 [https://t.co/I7xhXq0bzv](https://t.co/I7xhXq0bzv) [pic.twitter.com/yUhJSBkRRz](https://t.co/yUhJSBkRRz) > > — Dries Oeyen (@DriesOeyen) [22 december 2015](https://twitter.com/DriesOeyen/status/679229126352486400) ## What's next Commute has been growing at a steady clip since the end of 2015: there are now over 1500 people using the app. Even though all my optimizations went a long way, the service is on track to outgrow its free quota on several points. Therefore, my goal for 2016 is to launch some great premium features so Commute can become self-supporting. Either way, I'm incredibly grateful to be in a situation where I have such a luxury problem in the first place! Commute is a place where I can experiment and have fun, all while (hopefully) helping people steer clear of heavy traffic. Having had such a great experience developing for Pebble, I decided to get involved in something else as well. I had always been jealous of the various Meetup groups abroad, since it seemed like a ton of fun to meet up and share ideas and experiences with fellow Pebble developers. Interestingly, a fellow Belgian Pebble enthusiast apparently had the same idea, so we teamed up to become organizers of the Pebble Belgium Meetup group. [![Pebble Belgium Meetup](https://developer.repebble.com/assets/images/blog/guestblogdries_meetup.jpg)](http://www.meetup.com/PebbleBelgium/) Are you interested in Pebble development and do you happen to live in Belgium? We'd love to meet you! Our first event is planned for February 17 in Brussels and will involve talks, drinks and bites, socializing and some neat Pebble swag, all in a relaxed atmosphere. Both experienced Pebble developers and interested newcomers are welcome, we'll have something to offer regardless of your existing Pebble development experience. RSVP here: [http://www.meetup.com/PebbleBelgium/events/228117357/](http://www.meetup.com/PebbleBelgium/events/228117357/). Hope to see you there! * * * --- # Bringing the Family Back Together - 3.x on Aplite is Almost Here! Source: https://developer.repebble.com/blog/2015/12/02/Bringing-the-Family-Back-Together/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 02 December 2015 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/12/02/Bringing-the-Family-Back-Together.md#comments) ![](https://developer.repebble.com/assets/images/blog/3.x-on-tintin.png) The time has almost come for Pebble Classic and Pebble Steel to get firmware 3.x! This is great news for users who get access to all the improvements and fixes since 2.x, as well as new features such as timeline, Quiet Time, no more eight app limit, Standby Mode, and last but not least one new color (gray)! But let us not forget about Basalt and Chalk app developers, who also have a lot to get excited about - a huge new user base of Aplite watch wearers! This blog post aims to help developers using SDK 2.x features and APIs to migrate their apps to a single 3.x codebase. As well as bringing compatibility to all older watches when they upgrade to firmware 3.x, these changes will also serve to remove a lot of complex and ugly conditional code that has needed to exist in the meantime. Let's fix some apps! ## Get the Beta SDK To try out the beta SDK, read the instructions on the [SDK Beta](https://developer.repebble.com/sdk/beta) page. ## Mandatory Changes To be compatible with users who update their Pebble Classic or Pebble Steel to firmware 3.x the following important changes **MUST** be made: - If you are adding support for Aplite, add `aplite` to your `targetPlatforms` array in `appinfo.json`, or tick the 'Build Aplite' box in 'Settings' on CloudPebble. - Recompile your app with at least Pebble SDK 3.8 (coming soon!). The 3.x on Aplite files will reside in `/aplite/` instead of the `.pbw` root folder. Frankenpbws are **not** encouraged - a 2.x compatible release can be uploaded separately (see [_Appstore Changes_](#appstore-changes)). - Update any old practices such as direct struct member access. An example is shown below: - If your app uses either the [`Dictation`](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") or `Smartstrap` APIs, you must check that any code dependent on these hardware features fails gracefully when they are not available. This should be done by checking for `NULL` or appropriate `enum` values returned from affected API calls. An example is shown below: ## Appstore Changes To handle the transition as users update their Aplite to firmware 3.x (or choose not to), the appstore will include the following changes: - You can now have multiple published releases. When you publish a new release, it doesn’t unpublish the previous one. You can still manually unpublish releases whenever they want. - The appstore will provide the most recently compatible release of an app to users. This means that if you publish a new release that has 3.x Aplite support, the newest published release that supports 2.x Aplite will be provided to users on 2.x Aplite. - There will be a fourth Asset Collection type that you can create: Legacy Aplite. Apps that have different UI designs between 2.x and 3.x on Aplite should use the Legacy Aplite asset collection for their 2.x assets. ## Suggested Changes To fully migrate to SDK 3.x, we also suggest you make these non-essential changes: - Remove any code conditionally compiled with `PBL_SDK_2` defines. It will no longer be compiled at all. - Ensure that any use of [`app_message_inbox_size_maximum()`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_inbox_size_maximum "app\_message\_inbox\_size\_maximum") and [`app_message_outbox_size_maximum()`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_outbox_size_maximum "app\_message\_outbox\_size\_maximum") does not cause your app to run out of memory. These calls now create [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") buffers of 8k size by default. Aplite apps limited to 24k of RAM will quickly run out if they use much more memory. - Colors not available on the black and white Aplite display will be silently displayed as the closet match (black, gray, or white). We recommend checking every instance of a `GColor`to ensure each is the correct one. - In addition to the point above, investigate how the contrast and readability of your app can be improved by making use of gray (either `GColorLightGray` or `GColorDarkGray`). Examples of this can be seen in the system UI in the banner at the top of this blog post. - Apps using image resources should take advantage of the new `bitmap` resource type, which optimizes image files for you. Read the [_Unifying Bitmap Resources_](https://developer.repebble.com/blog/2015/12/02/Bitmap-Resources.md) blog post to learn more. ## Questions? That's it! It will be quite straightforward to update most of your apps, but if you do have any problems or queries, feel free to [contact us](https://developer.repebble.com/contact.md) or find us on Discord. * * * --- # Unifying bitmap resources Source: https://developer.repebble.com/blog/2015/12/02/Bitmap-Resources/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 02 December 2015 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/12/02/Bitmap-Resources.md#comments) With the upcoming release of firmware 3.8 on Pebble and Pebble Steel, and the associated SDK 3.8, we have decided to redesign how image resources work in Pebble apps. ## Why change? First, some history: when SDK 1 was originally released, and continuing up to SDK 2.9, there was a single image type, `png`. A `png` resource would take a PNG as input and spit out a custom, uncompressed, 1-bit-per-pixel image format we called "pbi". This was the only bitmap format Pebble supported, and life was simple. With the release of SDK 3.0, we added firmware support for a new image format: PNG (with some restrictions). This enabled Pebble to directly read compressed images, and those images could be 1-bit, 2-bit, 4-bit or 8-bit palettised. The existing `png` resource type was changed to produce these images instead of the old PBI format, and everyone had smaller image resources. Unfortunately, "png" isn't the best option for all cases. The old 1-bit format supported some [compositing operations](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp "GCompOp") that other image formats do not support. We added the `pbi` format to achieve this legacy behavior. Additionally, PNG decompression isn't free: loading a PNG resource requires enough memory to hold the compressed image, the uncompressed image, and some scratch space. This was often offset by the benefits of palettised images with fewer bits per pixel, but sometimes it didn't fit. We added `pbi8`, which produced 8-bit-per-pixel PBI images. This still left it impossible to generate a palettized PBI, even though the format does exist. As a further complication, since SDK 1 and continuing through the present day, `pbi` (and `pbi8`) images have cropped transparent borders around the outside. However, `png` images (as of SDK 3) do _not_ crop like this. The cropping behavior was originally a bug, and is generally undesirable, but must be maintained for backwards compatibility. There is one additional exception to all of this: until SDK 3.8, the Aplite platform still interprets `png` to mean `pbi`. It also interprets `pbi8` to mean `pbi`. When we built the 3.8 SDK, we changed `png` to really mean `png` on Aplite. Unfortunately, the more limited memory of the Aplite platform meant that these PNGs sometimes did not have enough space to decompress. The only workaround was to duplicate resources and use `targetPlatforms` to specify a `pbi` resource for Aplite and a `png` resource for Basalt and Chalk. The easiest answer was to keep `png` as an alias for `pbi` on Aplite—but then there's no way of generating a real PNG for Aplite. Furthermore, the `png`, `pbi` and `pbi8` trio was getting confusing, so we decided to do something else. ## "bitmap" to the rescue As of SDK 3.8, `png`, `pbi` and `pbi8` **are all deprecated**. We are instead introducing a new resource type, `bitmap`. This new resource type unifies all the existing types, allows the SDK to use its best judgement, increases the flexibility available to developers, and takes the guesswork out of advanced image manipulation. It also removes the generally undesirable forced cropping behavior. The simplest option is to only specify that you want a `bitmap` resource, and by default the SDK will do the most reasonable thing: ``` {"type": "bitmap","name": "IMAGE\_BERRY\_PUNCH","file": "images/berry-punch.png"} ``` This will create an image with the smallest possible in-memory representation, which will depend on the number of colors. On Aplite, where memory is tight, it will optimize for low memory consumption by creating a pbi. On all other platforms, where there is more memory to spare, it will create a png. This behavior can be overridden using the following attributes: - `memoryFormat`: This determines the `GBitmapFormat` that the resulting image will have. The default value is `Smallest`, which picks the value that will use the least memory. If you have specific requirements, you can specify: `SmallestPalette`, `1BitPalette`, `2BitPalette`, `4BitPalette`, `1Bit` or `8Bit`. If an image cannot be represented in the requested format, a build error will result. This, for instance, enables palette manipulation with static checking for confidence that you will have an appropriate palette to manipulate. - `spaceOptimization`: This determines whether we should optimize the image for resource space (`storage`) or memory (`memory`). The default depends on the memory available on the platform: Aplite defaults to `memory`, while other platforms default to `storage`. - `storageFormat`: This explicitly states whether an image should be stored on flash as a PNG or pbi image. In most cases you should not specify this, and instead depend on `spaceOptimization`. However, if you read resources directly, this option exists to force a value. So if you want an image that will always have a 2-bit palette and use as little memory as possible, you can do this: ``` {"type": "bitmap","name": "IMAGE\_TIME\_TURNER","file": "images/time-turner.png","memoryFormat": "2BitPalette","spaceOptimization": "memory"} ``` The resulting image will always have `GBitmapFormat2BitPalette`, even if it could have a 1-bit palette. If it has more than four colors, the build will fail. In SDK 3.8-beta8, this would result in a 2-bit palettized pbi. However, this is not guaranteed: we can change the format in the future, as long as the result has [`GBitmapFormat2BitPalette`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmapFormat2BitPalette "GBitmapFormat2BitPalette") and we prefer to optimise for memory consumption where possible. Finally, note that some combinations are impossible: for instance, a `1Bit` image can never be stored as a PNG, so `"storageFormat": "png"` combined with `"memoryFormat": "1Bit"` will be a compile error. ## Migration If you have an existing app, how do you migrate it to use the new `bitmap` type? If you were depending on the `pbi` cropping behavior, you will have to manually crop your image. Beyond that, this table gives the equivalences: | SDK 3.7 type | `bitmap` specification | | --- | --- | | `png` | `{"type": "bitmap"}` | | `pbi` | `{"type": "bitmap", "memoryFormat": "1Bit"}` | | `pbi8` | `{"type": "bitmap", "storageFormat": "pbi"}` | `png-trans` has been left out of this entire exercise. Its behavior is unchanged: it will produce two pbis with `GBitmapFormat1Bit`. However, `png-trans` is also deprecated and discouraged. As of SDK 3.8, all platforms support transparency in images, and so should use `bitmap` instead. If you have any questions, you can find us on Discord or [contact us](https://developer.repebble.com/contact.md). * * * --- # Introducing Pebble Tool 4.0 Source: https://developer.repebble.com/blog/2015/12/01/A-New-Pebble-Tool/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 01 December 2015 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/12/01/A-New-Pebble-Tool.md#comments) I am pleased to today announce that version 4.0-rc4 of the `pebble` tool is now available. The key new feature is a new paradigm for dealing with firmware and SDK versions. This makes it much easier to deal with differing SDK versions, or to test code on multiple (emulated) firmware versions. _A note: while the tool is now at version 4.0, the SDK, firmware and mobile apps will not be following. Pebble tool versioning is now completely independent of the rest of the Pebble ecosystem._ ## Managing SDKs The pebble tool now manages SDKs for you, without you needing to download and install the entire SDK manually each time. The first time you need an SDK, the latest one will be automatically installed for you. After that, you can use the SDK operations that live under the `pebble sdk` subcommand. To see a list of available SDKs, use `pebble sdk list`: ``` katharine@kbrmbp ~> pebble sdk list Installed SDKs: 3.7 (active) Available SDKs: 3.6.2 3.4 3.3 3.2.1 3.1 3.0 2.9 ``` You can install any SDK using `pebble sdk install`, like so: ``` katharine@kbrmbp ~> pebble sdk install 3.6.2 Installing SDK... Do you accept the Pebble Terms of Use and the Pebble Developer License? (y/n) y Downloading... 100%[======================================================] 1.40 MB/s 0:00:01 Extracting... Preparing virtualenv... (this may take a while) Installing dependencies... Done. Installed. ``` You can switch between active SDKs using `pebble sdk activate `, like `pebble sdk activate 3.7`. Once you activate an SDK, it will be used for all `build` and `install` commands. ## Switching on the fly A number of commands now take an optional `--sdk` flag, which will override the current active SDK. This enables you to easily run one command with a different SDK version — for instance, compiling with 3.6.2 and then running on 3.7: ``` katharine@kbrmbp ~> pebble build --sdk 3.6.2 # ... katharine@kbrmbp ~> pebble install --emulator basalt --sdk 3.7 # ... ``` This is supported by `pebble build` as well as any command that supports `--emulator`. Additionally, you can now run emulators for multiple SDKs simultaneously by passing different values for `--sdk`. ## Benefits Beyond the obvious benefit of easier SDK management, the new system also produces much smaller SDKs. Each SDK used to be a 38 MB download, which decompressed to 143 MB, plus another hundred megabytes for the toolchain. Most of this is now downloaded only once, as part of the initial pebble tool setup. After that, each SDK is only a 2 MB download, which expands to 4 MB on disk. The new pebble tool can also alert you to new SDKs as they become available, enabling you to install them with a single command. ## Try it out! To try out our new pebble tool, read the instructions on the [SDK Beta](https://developer.repebble.com/sdk/beta) page. Please [contact us](https://developer.repebble.com/contact.md) if you run into any issues installing or using `pebble` v4.0, or if you have any feedback. You can also frequently find me on ~~Slack~~ Discord — join us! * * * --- # Nuance Brings Pebble The Freedom Of Speech Source: https://developer.repebble.com/blog/2015/11/10/Nuance-Brings-Pebble-The-Freedom-Of-Speech/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 10 November 2015 - [Jon Barlow](https://developer.repebble.com/blog/authors/jonb/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/11/10/Nuance-Brings-Pebble-The-Freedom-Of-Speech.md#comments) In October, we gave developers access to the microphone in the Pebble Time via our new [Dictation API](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation"), and almost instantly we began seeing awesome projects utilising speech input. Voice recognition is an exciting new method of interaction for Pebble and it has created an opportunity for developers to enhance their existing applications, or create highly engaging new applications designed around the spoken word. Speech-to-text has been integrated with Pebble by using the [Recognizer](http://www.nuance.com/for-business/automatic-speech-recognition/automated-ivr/index.htm) cloud service from Nuance, a leading provider of voice and language solutions. ## The Pebble Dictation Process The Dictation API has been made incredibly easy for developers to integrate into their watchapps. It’s also intuitive and simple for users to interact with. Here’s an overview of the the dictation process: 1. The user begins by pressing a designated button or by triggering an event within the watchapp to indicate they want to start dictating. The watchapp’s UI should make this obvious. 2. The watchapp [initiates a dictation session](https://developer.repebble.com/docs/c/Foundation/Dictation.md#dictation_session_start), assigning a [callback function](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSessionStatusCallback) to handle the response from the system. This response will be either successful and return the dictated string, or fail with an error code. 3. The system Dictation UI appears and guides the user through recording their voice. The stages in the image below illustrate: 4. Once the process has completed, the registered callback method is fired and the watchapp can deal with the response. ![dictation-flow](https://developer.repebble.com/assets/images/blog/dictation-flow.png) ## But How Does It Actually Work? Let’s take a closer look at what’s happening behind the scenes to see what’s really going on. ![dictation-recognizer](https://developer.repebble.com/assets/images/blog/dictation-recognizer.png) 1. To capture audio, Pebble Time (including Time Steel and Time Round) has a single [MEMS](https://en.wikipedia.org/wiki/Microelectromechanical_systems) microphone. This device produces output at 1 MHz in a [PDM](https://en.wikipedia.org/wiki/Pulse-density_modulation) format. 2. This 1 bit PDM signal needs to be converted into 16-bit [PCM](https://en.wikipedia.org/wiki/Pulse-code_modulation) data at 16 kHz before it can be compressed. 3. Compression is performed using the [Speex](http://www.speex.org/) encoder, which was specifically designed for speech compression. Compression needs to occur in order to reduce the overall size of the data before it’s transferred via bluetooth to the mobile application. Speex also has some additional advantages like tuneable quality/compression and recovery from dropped frames. 4. The mobile application sends the compressed data to Nuance Recognizer, along with some additional information like the user’s selected language. 5. Nuance performs its magic and returns the textual representation of the spoken phrase to the mobile application, which is then automatically passed back to the watchapp. 6. The Dictation UI presents the transcribed text back to the user where they can choose to accept or reject it. ## About the Dictation API Behind the scenes there’s a lot going on, but let’s take a look at how minimal the code needs to be in order to use the API. 1. Create a static variable as a reference to the dictation session: 2. Create a callback function to receive the dictation response: 3. Within a button click or other event, create a [`DictationSession`](https://developer.repebble.com/docs/c/Foundation/Dictation.md#DictationSession "DictationSession") to begin the process: 4. Before your app exits, don’t forget to destroy the session: ## Voice-enabled Watchapps Here’s a small sample of some of the watchapps which are already available in the Pebble appstore which utilise the Dictation API. - [Voice2Timeline](http://apps.repebble.com/en_US/application/561f9188bcb7ac903a00005b) is a handy tool for quickly creating pins on your timeline by using your voice. It already works in 6 different languages. You can leave notes in the past, or even create reminders for the future (e.g. “Don’t forget the milk in 1 hour”). - [Translate (Vox Populi)](http://apps.repebble.com/en_US/application/561ff3cbbcb7aca6250000a3) allows a user to translate short phrases and words into a different language. It uses the [Yandex](https://translate.yandex.com/) machine translator API which supports more than 60 different languages. - [Checklist](http://apps.repebble.com/en_US/application/5620e876768e7ada4e00007a) is a really simple tool which generates a list of items using your voice. It even allows you to enter multiple items at once, by specifying a comma or period. You can easily mark them as completed by pressing the ‘select’ button on each item. - [Smartwatch Pro](https://itunes.apple.com/gb/app/smartwatch-pro-for-pebble/id673907094?mt=8) (iOS) has been updated to give users voice controlled music playback, create reminders and even create tweets by using their voice. ## Final Thoughts Why not also checkout this video of Andrew Stapleton (Embedded Developer) as he deep dives into the internals of the Pebble Dictation API during his presentation at the [Pebble Developer Retreat 2015](https://developer.repebble.com/community/events/developer-retreat-2015.md). We hope you’ve seen how flexible and easy it is to use the new Dictation API, and perhaps it will inspire you to integrate voice into your own application or watchface - if you create a voice enabled watchapp, let us know by tweeting [@pebbledev](https://twitter.com/pebbledev). If you’re looking to find out more about voice integration, checkout our [developer guide](https://developer.repebble.com/guides/events-and-services/dictation.md), [API documentation](https://developer.repebble.com/docs/c/Foundation/Dictation.md "Dictation") and our [simple example app](https://github.com/pebble-examples/simple-voice-demo). We also have a very friendly and helpful Pebble community on Discord; why not join us? * * * --- # Migrating to Pebblekit iOS 3.0 Source: https://developer.repebble.com/blog/2015/10/29/ios-migration/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 29 October 2015 - [Alex Lin](https://developer.repebble.com/blog/authors/alex/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/10/29/ios-migration.md#comments) ![](https://developer.repebble.com/assets/images/blog/ios-migration-banner.png) Starting with Pebble Time Round, we are moving towards communicating with Bluetooth Low-Energy only. This means there are some updates to PebbleKit iOS to support this. Here are the major changes and steps to take to support the new BLE connection. **What's New** - Companion apps have dedicated, persistent communication channels - Start mobile apps from Pebble - 8K AppMessage buffers - Swift support ## What do you need to do? Import the new PebbleKit iOS 3.0 library into your project. ### API Changes #### NSUUIDs You can now use `NSUUID` objects directly rather than passing [`appUUID`](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md#//api/name/appUUID "appUUID") as a `NSData` object. **PebbleKit 2.x** ``` uuid\_t myAppUUIDbytes;NSUUID \*myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"];[myAppUUID getUUIDBytes:myAppUUIDbytes];[PBPebbleCentral defaultCentral].appUUID = [NSData dataWithBytes:myAppUUIDbytes length:16]; ``` **PebbleKit 3.0** ``` NSUUID \*myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"];[PBPebbleCentral defaultCentral].appUUID = myAppUUID; ``` #### Cold start PBPebbleCentral You'll want to start [`PBPebbleCentral`](https://developer.repebble.com/docs/pebblekit-ios/Classes/PBPebbleCentral.md "PBPebbleCentral") in a cold state now so users don't get a pop-up asking for Bluetooth permissions as soon as the app initializes. Call `[central run]` when it makes sense for the pop-up to show up. Add some custom text to the dialog with `NSBluetoothPeripheralUsageDescription` to your `Info.plist` file. **PebbleKit 2.x** ``` // MyAppDelegate.m - Set up PBPebbleCentral and run if the user has already// performed onboarding- (BOOL)application:(UIApplication \*)application didFinishLaunchingWithOptions:(NSDictionary \*)launchOptions {[PBPebbleCentral defaultCentral].delegate = self;[PBPebbleCentral defaultCentral].appUUID = myAppUUID;if ([MySettings sharedSettings].userDidPerformOnboarding) {[[PBPebbleCentral defaultCentral] run];}} ``` **PebbleKit 3.0** ``` // MyOnboarding.m - Once the pop-up has been accepted, begin PBPebbleCentral- (IBAction)didTapGrantBluetoothPermissionButton:(id)sender {[MySettings sharedSettings].userDidPerformOnboarding = YES;[[PBPebbleCentral defaultCentral] run]; // will trigger pop-up} ``` ### Specify that your app is built with PebbleKit 3.0 in the Developer Dashboard Go to edit the companion app listing in your [developer dashboard](https://developer.repebble.com/dashboard) page and check the box for "Was this iOS app compiled with PebbleKit iOS 3.0 or newer?" This way, users on Pebble Time Round will be able to see your app in the appstore. ![](https://developer.repebble.com/assets/images/blog/checkbox.png) ### Final thoughts With a few quick steps, you can bring compatability for the BLE connection to your app. In the coming months, we'll be rolling out updates for users of Pebble and Pebble Time to take advantage of BLE-only connection as well. In the short term, if you intend to support Pebble Time Round, these steps are mandatory. For the complete details on migrating to PebbleKit 3.0, take a look at our migration guide. If you have any issues in migrating or have any questions concerning PebbleKit 3.0, feel free to [contact](https://developer.repebble.com/contact.md) us anytime! * * * --- # Pebble Time Round SDK - Developer Preview 2 Source: https://developer.repebble.com/blog/2015/09/23/Pebble-Time-Round-SDK-Developer-Preview-2/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 23 September 2015 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/09/23/Pebble-Time-Round-SDK-Developer-Preview-2.md#comments) ![](https://developer.repebble.com/assets/images/blog/ptr-dp2-cover.jpg) You may have heard about [Pebble Time Round](https://rePebble.com/timeround), our newest addition to the Pebble family which adds a whole bunch of new possibilities, challenges, and opportunities for our dedicated developers to get creative with. With a new platform usually comes a new SDK to play with. Well, why wait? All developers can now get their hands on the new [SDK 3.6-dp2](https://developer.repebble.com/sdk.md) or use it on [CloudPebble](https://cloudpebble.repebble.com/). This is a preview of the first Pebble Time Round-compatible SDK, allowing developers to realize their ideas for new and improved watchapps and watchfaces. Have a favorite classic round watchface that looks cumbersome on a rectangular display? Try it out on this one! ## New Resources To get you started, we have updated many sections of the Pebble Developer site with new content and information on designing and developing for the new Pebble hardware platform, code-named Chalk. Read more on the [_Getting Started with Pebble Time Round_](https://developer.repebble.com/sdk/round-getting-started) page. ## Virtual Lab Want help getting started with the Pebble Time Round SDK? Join us on September 24th at 1pm PDT in our Slack channel for a virtual lab with the Pebble Dev team. Bring all your questions, and we'll do our best to answer them! ## Compatiblity Note Bear in mind, this is a **developer preview** , meaning that apps created with this SDK should **not** be uploaded to the Pebble appstore, as it is subject to possible breaking changes in new APIs before the final release of SDK 3.6. Thanks for your cooperation! * * * --- # 5 Bits from Pebble Rocks Boulder Source: https://developer.repebble.com/blog/2015/09/11/boulder-hackathon/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 11 September 2015 - [Alex Lin](https://developer.repebble.com/blog/authors/alex/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2015/09/11/boulder-hackathon.md#comments) ![](https://developer.repebble.com/assets/images/blog/prb-cover.jpg) Our first steps down the red brick paved Pearl St in downtown Boulder Colorado set the tone for the weekend. The streets were bustling with activity. Street musicians were setting up their stage. Students and locals alike were just getting started with their Friday evening, funneling into the many restaurants and bars that lined the street. We had a different target, [Galvanize-Boulder](http://www.galvanize.com/), the coworking space that was the venue of Pebble's first hardware hackathon, Pebble Rocks Boulder. This would prove to be one of the most successful and worthwhile events we've had the pleasure of attending. Here are 5 ideas on what made Pebble Rocks Boulder great. ## Eyes on the Prize ![](https://developer.repebble.com/assets/images/blog/prb-altitude.jpg) Two of the broad goals of why we attend hackathons has always been to evangelize our platform and to interact with developers to improve our tools, documentation and APIs. In this case, our goal was more targeted, to unveil the smartstrap APIs to the public for the first time. A hackathon was the perfect opportunity to see first hand how developers interact with the APIs and what experiences they had in mind for smartstraps. All of this would go towards improving our platform to better empower developers to create new smartstraps. ## Keep It Simple ![](https://developer.repebble.com/assets/images/blog/prb-fitpay.jpg) With this goal in mind, [Viget](https://viget.com/) helped us to plan out a schedule of events. We’ve sponsored dozens of hackathons in my time at Pebble; from smaller engagements of ~120 students to stadium filled events with over 2000 hackers. At Pebble Rocks Boulder, we made it more intimate; 25 teams of 3–4 members each and we kept it simple. Nerf gun fights didn't erupt at midnight, and we didn't have any rap battles. But we did have a group of developers with a diverse set of backgrounds that were condusive to a productive, creative environment. ## Diversify ![](https://developer.repebble.com/assets/images/blog/prb-intro.jpg) Having a diverse group of hackers catalyzed great discussion and sharing of ideas. We invited iOS developers, networking engineers, electrical engineers, designers and more. They came from all across the US from places like Portland, New York and Texas. ![](https://developer.repebble.com/assets/images/blog/prb-hacks.jpg) A team from [Fitpay](http://www.fit-pay.com/) brought their expertise in the world of secure, contactless payments. Over the course of the weekend, they integrated their APIs with Pebble, creating the first contactless [payment system on Pebble](https://www.hackster.io/team-fitpay/pebble-smartstrap-nfc-payments)! Another team of students and graduates from the [Turing School](http://turing.io/) brought the biking culture of Boulder to play by developing a dock for the Pebble. [This dock](https://www.hackster.io/teamturing/turnakit) provided turn-by-turn navigation and automatic turn signals to keep bikers safer. A team of survivalists created a [SurvivalStrap](https://www.hackster.io/team-universal-mind/survival-strap), with an integrated UHF transmitter for the stranded hiker. Two developers who had little hardware experience prior were able to integrate a GPS module and SD storage to create a untethered solution for a fitness/run tracker, complete with music playback. [Tackmobile](http://tackmobile.com/) paired the Pebble platform with [littleBits](http://littlebits.cc/) to create a convenient way to [prototype smartstrap ideas](https://www.hackster.io/team-tack/pebblits). We couldn't possibly list all 23 hacks. You can see the complete, open-sourced projects on [Hackster.io](https://www.hackster.io/hackathons/pebble-rocks-boulder/a-pebble-hackathon/projects). ## Get Out There, and Stay Up ![](https://developer.repebble.com/assets/images/blog/prb-pebble1.jpg) Often times, we see sponsors show up, give a talk and then retire for the evening. We take a different approach at Pebble. Some of the craziest ideas manifest themselves at 2AM. Even more, we make the rounds, see everyone's progress, and do what we can to push ahead and make progress. As activity dies down and you only hear a few solitary taps of the keyboard here and there, we find more one on one time with certain people, or we might find some inspiration for ourselves. After all, a hackathon is all about pushing ourselves to create something new in unfamiliar environments and circumstances. Why not join in ourselves? ## All Good Things Come to an End ![](https://developer.repebble.com/assets/images/blog/prb-closing.jpg) The morning after the event, we 6 bleary eyed Pebblers made the drive to Denver International Airport. Our stomachs were full from the celebratory burgers and whiskey milkshakes from the night before. The stalwart peaks of the Colorado Rockies passing by contrasted with the landscape of flashing LEDs, 3D printed parts and hardware from the days before. We recounted all the great projects we saw and discussed ideas that were fresh in our minds. Finally we thought of the last minute rush to get things out the door and the last few hours of Pebble Rocks Boulder. It ended in a flurry of activity as judges and hackers made their rounds, eager to learn more about each project. As everything wound down, contacts were exchanged, new-found friends said their goodbyes, and we closed out our conversations; learning as much as we could about their experiences. In the end, it wasn't about winning the top prize or creating the most commercially viable hack. People found value in the shared experience of working together on a new platform. All of this resulted in one of the most rewarding hackathons we've been a part of. By identifying the goals and working with the great team at Viget, we brought together a diverse group of people to build on our platform. Through this process, we learned a great deal about the smartstrap apis and generated brand new ideas of how to apply them. We are eager to attend more events like this in the future. This was just the beginning of what we wanted to achieve with smartstraps. If you've got an idea or want to try your hand at smartstraps, check out the [guides](https://developer.repebble.com/guides/smartstraps.md) and feel free to [reach out to us](https://developer.repebble.com/contact.md) for anything! ## Special Thanks - [Viget](https://viget.com/) for coordinating this entire event. - [Galvanize-Boulder](http://www.galvanize.com/) for hosting us in their Boulder office. - [23rd Studios](http://www.23rdstudios.com/) for their amazing video and photo coverage of the event. - [Lulzbot](https://www.lulzbot.com/) for providing their 3D printers and personally helping teams to get their models printed. * * * --- # Guest Post - A Reflection on a Pebble Hackathon Source: https://developer.repebble.com/blog/2015/08/24/time-to-code-hackathon/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 24 August 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2015/08/24/time-to-code-hackathon.md#comments) ![](https://developer.repebble.com/assets/images/blog/TTC-participants.png) _This is a guest post by Matt Langlois, developer of the Pebble [Meetup](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md) app, contributor to pebble.js, and active Pebble developer._ The [Time to Code](http://www.meetup.com/PebbleOTT/events/222726467/) hackathon in Ottawa, Canada on July 11th and 12th was a huge success! We had developers from all over the city come and join us to create their first app. We even had a developer who had never touched code before join us to create his first app ever! **Schedule** Saturday started off with a wonderful presentation by Cherie from Pebble’s California HQ teaching people how to create their [first watchface](https://github.com/cjwilliams/watchface-demo). The presentation was spot on, and she even had a shout out to my Meetup app when she was discussing the timeline. The rest of the day was spent creating apps and turning coffee and snacks into code. The second day we let the developers work on their projects all day. Jonathan, the creator of Music Boss and Ottawa’s Pebble Rockstar, also stopped by on Sunday to speak with the developers and check out some of the creations. In the afternoon developers had a chance to present their hacks and creations to the panel of judges. **Winners** ![](https://developer.repebble.com/assets/images/blog/TTC-winners.png) The winners, in order from right to left. Some pretty amazing ideas became a reality at this event. Here's a rundown of our winners: Kurt (MetaDark) created his first ever app for the Pebble platform this weekend. He designed a game called [circle attack](https://apps.getpebble.com/en_US/application/55a3377ec643f9989b00008d). His app makes use of plenty of API functionality such as: colors, animations, button events, and randomization. I had a chance to speak with the developer and it sounds like he really thought his app through by optimizing it in every way possible to use up as little resources as possible making it run just as well for Aplite. He was the winner of a Pebble Time. Jaime took his first shot at Javascipt using PebbleJS to create his food truck app. Check out the source [here](https://github.com/jaimeyu/foodtruck-pebble). His app is more of a utilitarian app providing a list, sorted by distance, of the nearest food trucks. For his app, Jaime won a Pebble Steel. Alex was the creator of a watchface known as SpringPebble. It was one of the few watchfaces created during the event and the judges really liked it. It made use of animations and colours in a harmonizing way. For his watchface Alex won an Original Pebble. Finally, we decided to choose Joseph as a runner up - he made a 5 pin bowling app. This app had a very fluid user experience but was unfortunately not fully complete, hence the reason he was the runner up. Other than the above listed winners we also had some other pretty creative ideas! A group of 3 friends created a way to input text using only gestures. Another developer created a way to setup a home location so you will never miss the bus at that stop! There were also some games and other watchfaces created which I haven’t seen on the Pebble before. **Looking Forward** Hopefully, the development doesn’t stop here for these Pebblers. I look forward to seeing further refined versions of their apps on the Pebble appstore. What a great event - I hope I am able to run an event like this in the future! * * * --- # Pebble Timeline Challenge Week 12 Winner - TV Shows! Source: https://developer.repebble.com/blog/2015/07/24/timeline-challenge-week-twelve/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 24 July 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/07/24/timeline-challenge-week-twelve.md#comments) Since we announced the Pebble timeline, getting timeline pins about your favorite TV shows has been a much requested use case. Now, with the 12th and final winner of the timeline challenge, you can! With [TV Shows](https://apps.getpebble.com/en_US/application/55a461f5c12bc1287100007c), get timeline pins and notifications from up to 25 shows. [Edwin Finch](http://www.edwinfinch.com), one of our most devoted and active developers, created this app with the design help of [Philipp Weder](http://www.iphibse.ch), his partner in all things Pebble development. Together, they created the [Lignite collection](http://lignite.io/), a series of watchfaces designed and optimized for the Pebble Time. They have also collaborated on a number of other watchfaces and watchapps, which you can read more about [here](https://www.edwinfinch.com/pebble/). To make this app, Edwin and Philipp used a number of Pebble features as well as created some of their own. - [PebbleKit JS](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) - Many APIs from the [Pebble C SDK](https://developer.repebble.com/sdk.md) - A self designed server that includes JavaScript, PHP, Node.js, MongoDB, and HTML/CSS for the webpage. Below, Edwin and Philipp tell us about their experiences with Pebble and Pebble development! ### Q&A with Edwin and Philipp **Tell us about your experiences as Pebble Developers.** _Edwin_ I got a Pebble when the original Kickstarter classics started shipping, but never got around to SDK 1.x. A few months after SDK 2.0 came out(which was a game changer), in January 2014, I just started messing around with it. I found it really easy to get started: CloudPebble helped me through getting set up because it’s so easy to use, and I messed around with other peoples’ open source projects. In one weekend I’d created my first two watchfaces: [Clean & Simple](https://www.edwinfinch.com/pebble/watchfaces/cleanandsimple/) and [PebbleBoy](https://www.edwinfinch.com/pebble/watchfaces/pebbleboy/). I eventually asked the community for ideas to create watchfaces and apps, and I’d do it for free, just because it was fun to do. This is when I met Philipp, who’d mocked up some concepts which I was really looking forward to writing and did end up writing. We ended up working together for weeks, which turned into months, and are now close friends. We created Lignite - a successful Kickstarter campaign for a collection of watchfaces and apps for Pebble Time. Philipp created another amazing 20 designs for it, and I wrote the code. Lignite has companion apps for iOS and Android (because PebbleKit is multi platform, which is awesome) which are fully featured with settings and more. We’re still working on Lignite, and plan to add a lot more to it! _Philipp_ I actually only bought my Pebble for this very reason: creating my own watchfaces! Since I did not want to charge my smartwatch each and every day, a Pebble was the only logical smartwatch to buy. I was known for creating homescreens for the then-popular Windows Mobile smartphones many years ago. I ended up releasing over a hundred homescreens, which landed me some awesome freelance jobs for Microsoft, HTC, and the likes. Initially wanting to code my own watchfaces, I soon found out what I already expected to happen: I am not a real coder but rather a designer. This is where Edwin came in. I sent him my mockups, and that’s pretty much where it all took off. We released about 6 different watchfaces before the Kickstarter campaign for the Pebble Time started and gave us many more possibilities for designing watchfaces! Before the campaign even ended, we decided to launch our own campaign with Lignite. We were blown away by the feedback, the total amount pledged, and number of backers! We would have never expected such a need for custom watchfaces! **What is the backstory behind TV Guide?** _Edwin_ Philipp actually came up with the idea after brainstorming long and hard. We first discussed an app for managing your water consumption and reminding you to drink water throughout the day (another idea of Philipp’s). Luckily enough we had a more suitable idea for us and soon thereafter found out that [8-A-Day](https://apps.getpebble.com/en_US/application/5544c004946ce2b650000037) won with exactly the same idea (by the way - congrats to them on their win!). **Could you give us a few details about the creation of TV Guide? (any hurdles you ran into, etc)** _Edwin_ TV APIs are few and far between, especially APIs which you can use easily. After some research, we had found a scraper named Zap2XML which pulls data off of Zap2it and puts it into XML format. It was great because it provided key information like start and end times, 119 channels, etc. I took this and converted it into JSON format (which is easily readable for PebbleKit JS), and made a few optimization tweaks. I then built the internal API for it, which I use to pull the data and put it on the Pebble (and the website). I slowly built up the platform, first adding a list of channels, then show list based on channel, show detail view, and finally subscriptions. My original server backend was very messy and actually had an accounts system, which wasn’t user friendly and was very buggy. I ended up taking accounts out to improve the user experience. Philipp’s mockups featured many awesome features which we could not implement yet (since time was running out) but will later, such as images of the show right there on your wrist. We’re planning on expanding our feature set to match with his mockups eventually. **What was your favorite part about making the app?** _Edwin_ My favourite part about making TV Guide had to be just seeing all of that data pushed to my wrist. When I first saw all of the channels load into view, I had this rush of excitement and felt like I was in the future. Only a few years ago, smartwatches weren’t even a category (or to be more accurate, weren’t anything significant). Now, I am writing apps to display TV schedules on my watch, which is something else. With any project, I love seeing it grow, in files and features. **Were there any significant challenges you faced? If so, would you like to expand upon them?** _Edwin_ Yes, I faced a couple of significant challenges. Mainly having to do with creating the mobile web version of the platform, since I’m no web developer and a lot of the concepts I worked with were fairly new to me. I also hit a few scaling problems. Dealing with between 7000-9000 TV shows can prove slow and cumbersome. For example, it takes about 80-85 minutes total to run through each show and push the pin for that respective show (and that’s in best case scenario with a lower amount of shows). A small annoying problem: at early stages of development, I had to wait for all 119 channels to load each time I wanted to test the app once. While all of these challenges were frustrating, they sure were a blast to deal with. And seeing all of that data at my wrist made me feel awesome! **Do you have any other app ideas that you’re planning on exploring in the future?** There was another idea which we had to put on hold because it would have required more time than we had. But we have put it on our todo list and are sure that we will release it one day. Maybe on the next timeline contest? :-) And of course loads more watchfaces and apps for the Lignite Collection. Philipp has designed many more faces/apps in the meantime and we are not running out of things to do in the future with Pebble! **Anything else you’d like to tell us?** _Edwin_ Thanks for making an awesome platform. I don’t know where I’d be if it weren’t for Pebble. I wouldn’t have met Philipp, went to Austin to teach people how to write Pebble apps (for Austin Spark League), or have written this. Writing apps for Pebble has completely changed my life and I’m thankful every day for it. Keep making awesome happen! ![](https://developer.repebble.com/assets/images/blog/edwin-and-philipp.png) ### A Great 12 Weeks That's a wrap! We've had a great 12 weeks, and were amazed by the apps that our developers submitted. Keep an eye out over the next few weeks for some honorable mentions as well as special features! To everyone that participated in the timeline challenge, thanks for showing us how you **#makeawesomehappen**. ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) - Week 5: [Flights](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md) - Week 6: [Meetup](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md) - Week 7: [Greeney's Run](https://developer.repebble.com/blog/2015/06/18/timeline-challenge-week-seven.md) - Week 8: [Pokedex Challenge](https://developer.repebble.com/blog/2015/06/25/timeline-challenge-week-eight.md) - Week 9: [Tideline](https://developer.repebble.com/blog/2015/07/02/timeline-challenge-week-nine.md) - Week 10: [8-A-Day](https://developer.repebble.com/blog/2015/07/09/timeline-challenge-week-ten.md) - Week 11: [WatchPop](https://developer.repebble.com/blog/2015/07/17/timeline-challenge-week-eleven.md) * * * --- # Pebble Timeline Challenge Week 11 Winner - WatchPop! Source: https://developer.repebble.com/blog/2015/07/17/timeline-challenge-week-eleven/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 17 July 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/07/17/timeline-challenge-week-eleven.md#comments) ![](https://developer.repebble.com/assets/images/blog/watchpop-banner.png) At its core, Pebble is personal and customizable: with thousands of apps and watchfaces to choose from, it's likely that no two Pebblers have the same experience. Now, with the 11th winner of the Pebble Timeline Challenge, you have the opportunity to personalize your Pebble even further: add your beloved pictures, and show off your customized, beautiful watchfaces! WatchPop gives users the chance to make a watchface out of their personal pictures. The attention to detail is what has set this app apart and made it a favorite among Pebblers. With features like color matching, WatchPop's watchfaces are well designed and aesthetically pleasing. ![](https://developer.repebble.com/assets/images/blog/watchpop-color-matching.png) A group of three developers (Adam Fourney, Mike Terry, and Kent Lyons) met through the computer science research community and worked in tandem on this app. Adam and Mike also work together in their professional lives: Mike is Adam's PhD advisor! Here are some of the features and frameworks they used to make WatchPop a reality: - [PebbleKit JS](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) - [PebbleKit Android](https://developer.repebble.com/guides/communication/using-pebblekit-android.md) - [ImageMagick](http://www.imagemagick.org/script/index.php) to create, edit, compose, or convert bitmap images. Read on to hear more about WatchPop and its developers! ### Q&A with WatchPop **Tell us about your experiences as Pebble developers.** We've been developing for the Pebble for about 8 months now. We've found the Pebble development environment to be really easy to work with, especially given PebbleKit JS and [CloudPebble](https://cloudpebble.repebble.com/). With these tools, you can quickly prototype and test out new ideas, which is great. We're also huge fans of the Pebble platform itself - it is a very open platform. This openness really encourages experimentation, letting you explore all the ways smart watches can fit into people's lives. **What spurred the development of WatchPop?** Our first watch face was Sunshine, which shows the latest solar flare images captured by NASA. Almost as soon as we released it, people started asking about how they could put other pictures on their watch. In retrospect this was the obvious thing to do -- if we could deliver satellite imagery to your watch, we should be able to put pictures from your phone on your watch. So we created WatchPop. **Tell us a bit about developing WatchPop.** WatchPop really pushes the limits of the platform in a lot of ways — especially on Aplite. We move a lot of bytes around to make pictures appear on your wrist, and we have to do it without a lot of memory, bandwidth, or CPU to spare. This forces you to get creative, which can be fun in its own right. Working within those constraints was challenging at times, but the dev community really provided great advice to help us when we hit roadblocks. **What was your favorite part about making the app?** Our favorite part of making the app has been the feedback we've received from our users. People really seem to cherish the ability to have pictures of their loved ones on their Pebble. It seems to make the Pebble that much more personal for them. We've also been surprised at some of the creative uses of WatchPop. We've had a user put his company logo on his wrist using WatchPop, and a tattoo artist put a mini portfolio of her work on her Pebble with WatchPop. We also attended a wedding where a common graphic appeared on the invitation and program. We took a picture of it, and it became our new watch face. People seemed to think that it was a nice way to celebrate the event. In all these cases the common element is that it's a relatively instant process -- you take a picture, and it shows up on your watch -- so it makes it easy to use it in creative ways. **Do you have any other app ideas that you're planning on exploring in the future?** One of the features we've been working on are picture feeds. We already have one feed, which spotlights cool pictures we come across. A couple of weeks ago, we realized we had an opportunity to do something cool with the feed: we could rebroadcast images of the New Horizons Pluto fly-by to peoples' Pebbles, so we've been doing that for the past week. This took off! (forgive the pun, but we are huge space nerds) We've received a lot of positive feedback, with requests for a permanent space-themed feed. There's just something about cool about images being captured billions of miles away, and showing up on your wrist. Given this experience, we're really interested in other types of content or feeds that people would like to have on their Pebble. We think there is a huge space of content that people would like to see updated on a regular basis on their Pebble. To support this, we've started exploring an API that lets developers send pictures or other content to people's watches using the WatchPop backend. We'd love to talk to people who'd be interested in using such a service. **Anything else you’d like to tell us?** We're really honored and excited to have received this award! We're big fans of Pebble and this platform! ![](https://developer.repebble.com/assets/images/blog/watchpop-guys.png) ### The Final Countdown The timeline challenge has almost wrapped up, but there's still **ONE** chance left to win! Make sure you submit your apps by 11:59 pm PST this Sunday, July 19th, and show the world how you **#makeawesomehappen**. ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) - Week 5: [Flights](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md) - Week 6: [Meetup](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md) - Week 7: [Greeney's Run](https://developer.repebble.com/blog/2015/06/18/timeline-challenge-week-seven.md) - Week 8: [Pokedex Challenge](https://developer.repebble.com/blog/2015/06/25/timeline-challenge-week-eight.md) - Week 9: [Tideline](https://developer.repebble.com/blog/2015/07/02/timeline-challenge-week-nine.md) - Week 10: [8-A-Day](https://developer.repebble.com/blog/2015/07/09/timeline-challenge-week-ten.md) * * * --- # Pebble Timeline Challenge Week 10 Winner - 8-A-Day! Source: https://developer.repebble.com/blog/2015/07/09/timeline-challenge-week-ten/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 09 July 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/07/09/timeline-challenge-week-ten.md#comments) ![](https://developer.repebble.com/assets/images/blog/8-a-day-collage.png) Doctors have been telling us this for years: drink 8 glasses of water a day, preferably more. Now, with the 10th winner of the Pebble Timeline Challenge, [8-A-Day](https://apps.getpebble.com/en_US/application/5544c004946ce2b650000037), you can have your Pebble remind you instead! Get pinged hourly, and use your timeline to stay healthy and hydrated. At 9 AM in your local time zone, you'll receive a notification reminding you to drink your first glass of water. You can input whether or not you've finished a glass, or choose to stop the notifications. If you continue receiving notifications, a pin will appear in an hour reminding you to continue drinking water. This pattern will continue throughout the day, and at the end of the day you'll receive a summary of your daily water intake. An Australian developer, Stephen Rees-Carter, developed this app after conversations with his wife about the idea. A web developer by training, he was excited to use his PHP skills to create an app for the timeline. Here are some of the features he used in his application. - [Timeline](https://developer.repebble.com/guides/pebble-timeline.md) for reminders, notifications, and updates - [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) for external API calls - [PHP API Wrapper](https://github.com/valorin/pinpusher) - An external PHP Application for logging and reminding the user Read on to hear more about Stephen and his app. ### Q&A with Stephen **Tell us about your experience with Pebble development.** It's been surprisingly easy to pick it up. A few months ago I hadn't even touched a Pebble, let alone done any Pebble development! I loved the idea of the timeline when the kickstarter campaign was launched, so I backed it, and when the timeline challenge was announced I thought to myself: I'm a web developer, why not give it a go, and so I did. The C proved to be the hard part, since as I'm a PHP developer, it proved to be frustratingly basic, but the Javascript was familiar territory. That said, getting my head around the split between C on the watch and JS on the phone took some effort, but once I got that sorted, it was good fun, and the SDK and CloudPebble made things easy. After I completed 8-A-Day, I had an idea for a multi-player game built within the timeline, [Timeline Tag](https://apps.getpebble.com/en_US/application/5551e85defff0cc943000026). As I'd already built my infrastructure for 8-A-Day, it took very little effort to build the game and get it live - which I was pretty happy about, and it shows how easy it is to get an app going. **What is the backstory behind 8-A-Day?** My wife and I were learning Android development when I decided to go for the challenge, and she had a great idea for an Android app that reminds you to drink water throughout the day... so I shamelessly stole it, as I thought it'd be an awesome idea for a Pebble Timeline app. It turns out that it works really well in the timeline, and a number of the issues that we were having with that Android app were non-issues in the timeline. For example, it's easy to go back, or forward, in time to find a notification when you have another drink - something you can't with a traditional Android notification. **Tell a bit about the creation of 8-A-Day.** I figured that since the timeline API is a web based system, it would make sense for me to build a simple PHP API server (using Laravel's Lumen, for those that are interested) to power the app. This turned out to be a good decision, as I only had to do minimal work on the C and JS parts of the Pebble app - most of the logic is actually in my PHP app, which was easier for me to work with, and it meant I was able to spend more time optimising the look and feel of the timeline pins. Once I had a working app and server, the next step was testing... If you've ever worked with an app that has to fire notifications at specific times of the day, and handle repeating notifications within a specific time window, you'll know it's really, really hard to test! You have to spend days just watching and remembering what you've done with the app to make sure it's doing what it's supposed to be doing. It's worse when you're stuck using an emulator, and you need to leave your computer frequently during the 11 hour window that the app is running. **What were some challenges you faced?** I faced two challenges with 8-A-Day. The first one was dealing with timezones. The timeline API accepts pins with UTC timestamps, however 8-A-Day creates pins at 9am and 8pm in the local time of the Pebble. After a bit of searching I found a way to get the minute offset amount, and now every request to my server contains the offset so the server can figure out what timestamp to use. The second challenge was getting the watch to communicate with the phone (well, emulator, at the time) as it only seemed to work occasionally. After some searching, I found a suggestion to send a ping from the phone to the watch to let it know when it's ready. Once I implemented this, everything else fell into place. **What was your favorite part about making the app?** Since 8-A-Day relies on my server, I was able to tail the logs and watch what was happening. I was ridiculously excited when after publishing the app in the store, people actually started installing and using it! I went to bed the first night after publishing it, and woke to find a number of new users had installed it. I have since heard from a number of people who love 8-A-Day and find it useful. That's by far my favourite part of making the app. **Any other applications or projects that you'd like to talk about?** I'm planning on an advanced version of 8-A-Day that allows you to create multiple reminders for different things, and configure specific daily targets and start/end times. For example, if you have to take a specific medication 4 times a day, you could configure it to remind you to take your tablets AND drink water throughout the day. :-) I'd also love to explore ideas around multi-player games/social apps within the timeline, expanding on the concept I've played with in my Timeline Tag game. So rather than simply interacting with a random number generator, your actions influence what other players receive on their Pebble. **Anything else you’d like to tell us?** I just wanted to finish up by saying that I love the concept of the timeline. It's the perfect companion to the watch form factor, and I'm always interested to hear about apps that are using it - especially some of the more creative and out-of-the-box ideas like the [Pokedex Challenge](https://apps.getpebble.com/en_US/application/5570838dddc5cc8060000012). It's really easy to build timeline apps, so if you have any creative, or crazy ideas, go for it! :-) ![](https://developer.repebble.com/assets/images/blog/stephen-rees-carter.png) ### 2 Weeks! The Pebble Timeline Challenge is almost over! With only two weeks left, you don't want to delay your participation any more. [Publish](https://dev-portal.getpebble.com/) your apps, [submit](https://docs.google.com/forms/d/1chfXdxYy1vsu9s_qLzpvqveJ6UiVuiALLScCumIWHL0/viewform?c=0&w=1) your entries, and **#makeawesomehappen**. ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) - Week 5: [Flights](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md) - Week 6: [Meetup](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md) - Week 7: [Greeney's Run](https://developer.repebble.com/blog/2015/06/18/timeline-challenge-week-seven.md) - Week 8: [Pokedex Challenge](https://developer.repebble.com/blog/2015/06/25/timeline-challenge-week-eight.md) - Week 9: [Tideline](https://developer.repebble.com/blog/2015/07/02/timeline-challenge-week-nine.md) * * * --- # Pebble Timeline Challenge Week 9 Winner - Tideline! Source: https://developer.repebble.com/blog/2015/07/02/timeline-challenge-week-nine/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 02 July 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/07/02/timeline-challenge-week-nine.md#comments) With summer in full swing, we're all itching to get to the beach. Use the 9th winner of the Pebble Timeline Challenge to find the best waves to sail, surf, or ride! Keep track of the local tides with [Tideline](http://apps.getpebble.com/en_US/application/552e04460835235bf50000b7) and make sure your sandcastles are never swept away again. Willem Olding, a Tasmanian developer and water sport lover, has avidly used his Pebble for years: check out this [video](https://player.vimeo.com/video/97312471) of him windsurfing with his Pebble. He created this app to help him plan when to windsurf, kitesurf, and fish. Below are some of the features he used to create his app. - [Timeline](https://developer.repebble.com/guides/pebble-timeline.md) to push new pins to the user daily - [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) - [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") - [Persistent Storage](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") to store the user's inputted information - [Geolocation API](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) to access the geolocation provided by the phone - [Animation](https://developer.repebble.com/guides/graphics-and-animations/animations.md) to create the rising and falling tide animations Below, Willem tells us a bit about his background. ### Q&A with Willem **Tell us about your experience with Pebble development.** My interest in Pebble was all about developing right from the start. I have created quite a few watch faces and apps over the years but Tideline is the first one I have released in the app store. I find developing for wearables to be a fun and unique experience. In only a couple of hours you can create a piece of software that you can carry around with you everywhere and use at any time. I am really excited to see what directions wearable technology takes in the future. **Why Tideline?** I have been playing around with creating a Pebble app for tide predictions for a while now. I’m really in to windsurfing, kitesurfing and fishing. Knowing what the tide is going to do is really important for planning these activities. There are quite a few good tide apps for Pebble already out there but none of them worked exactly how I wanted so I made my own. When I learned about the timeline API I knew it was a perfect fit. With pins for tide heights on the timeline you can plan your water sport activities around the rest of your schedule. **Could you give us a few details about the creation of Tideline?** The first step in creating Tideline was finding a high quality source of tide data. TideSpy was able to provide this data and the guys there kindly let me use the service for free. Without their support I would not have been able to support so many locations straight out of the box. Something that was important to me when developing Tideline was maintaining backward compatibility with the original Pebble and Pebble Steel. Although they don’t have timeline support yet they can still view the tide data within the app. I’m really glad I decided to support the earlier watches as at the time of writing the majority of users of Tideline are on the Aplite platform. **Did you face any significant challenges?** For me the most challenging part was developing the web backend that keeps track of the users and pushes out the new pins. I had never developed a web application before so it was a very steep learning curve. Fortunately Python has tools like Flask and SQLAlchemy which ultimately made the whole thing pretty straight forward. Another part of the project that I found challenging was doing the graphic design stuff. I want to thank my mate Lachy for giving me his time for free and designing a killer logo and banner. **What was your favorite part about making the app?** I really enjoyed developing on the Basalt emulator. It was great creating the animations and almost instantly being able to see what they look like on the watch. I actually created the whole app without ever laying hands on a Pebble Time so the emulator was a crucial part of the development process. **Do you have any other app ideas that you’re planning on exploring in the future?** I really love cooking so I think my next project will explore how wearables can improve the experience in the kitchen. I have a few ideas but I don’t want to say too much in case it doesn’t work out. There is also still plenty of things I want to add to Tideline such as support for more locations and an improved user interface. **Anything else you’d like to tell us?** I just wanted to say to any other developers out there who have created apps but are not sure if they should release them to the public. Its a great feeling knowing that other people out there are using and enjoying something you created. ![](https://developer.repebble.com/assets/images/blog/willem_olding.png) ### 3 Chances Left! We're nearing the finish line! It's been a great nine weeks, and we're so excited to see what these last three weeks bring. Make sure you [submit your apps](https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live.md) and put yourself in the running for some cool prizes and awesome swag! ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) - Week 5: [Flights](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md) - Week 6: [Meetup](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md) - Week 7: [Greeney's Run](https://developer.repebble.com/blog/2015/06/18/timeline-challenge-week-seven.md) - Week 8: [Pokedex Challenge](https://developer.repebble.com/blog/2015/06/25/timeline-challenge-week-eight.md) * * * --- # Guest Post - My Life As a Pebble Developer and Rockstar Source: https://developer.repebble.com/blog/2015/07/01/my-life-as-a-pebble-developer-and-rockstar/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 01 July 2015 - [Katherine McAuliffe](https://developer.repebble.com/blog/authors/katherine/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2015/07/01/my-life-as-a-pebble-developer-and-rockstar.md#comments) ![](https://developer.repebble.com/assets/images/blog/PebbleOttawaMeetup.png) _This is a guest post by Jonathan LaRocque, developer of [MusicBoss](https://play.google.com/store/apps/details?id=ca.rebootsramblings.musicboss), [MusicTime](https://play.google.com/store/apps/details?id=ca.rebootsramblings.musictime), [SimplyAlarm](https://play.google.com/store/apps/details?id=ca.rebootsramblings.simplyalarmholo) and Rockstar of the Pebble Developer Meetup Ottawa, [PebbleOTT](http://www.meetup.com/PebbleOTT/)._ April 2012 seems like such a long time ago. My first born is three years older, and now has a younger sibling. It also means that it has been three years since I contributed to the original Kickstarter campaign for Pebble. I was in the first batch of backers and received my Black Pebble early February 2013. This was the beginning of something special for me. After being introduced to my first computer when I was about 10 years old in elementary school, I’ve had an interest in computers and technology that has continued past my graduation from Computer Systems Engineering (Carleton University) in June 2007. A watch that connected to my phone that could do ...anything... was something I could only dream about. The funny thing is when I received my Pebble, I wasn't even developing; I hadn't touched a line of code between graduation and receiving my Pebble. Having the Pebble on my wrist changed something: the flow of ideas began, I greased my mental cogs, and started up the development machine. In the beginning, Pebble Development was an independent thing (for me anyways), it was still pretty new, and wasn't as well documented as it is now. But the community of developers for this platform grew quickly and superstars like Katharine Berry were pumping out open source examples for noobs like me to refer to. My first app, Simply Alarm, was my first crack at Android and Pebble development. The first version didn't even have a Pebble watch app, it just sent notifications to the Pebble when alarms rang. My second app, Music Boss, was born the way many great ideas are: while having a beer. I wanted to adjust the music volume on my phone while it was plugged into the sound system. I looked at my wife and swore: "When I get home, I am making an app that lets me adjust music volume with this thing [pointing at my Pebble]." This original idea for the application expanded greatly once I started developing. When I got my invitation to attend the first Pebble Developer Retreat in August 2013, I couldn't believe that I had been chosen. It was surreal: I was just an indie developer with a couple of apps from Ottawa, Ontario. I'll never forget the first time I met Eric; he joined us at a bar near our hotel the night before the opening day of the retreat. He showed me his Pebble with Music Boss running on it and asked me "Does it really have to say 'music volume' above the volume bar?" My conversation with Eric about how my app could be improved, in addition to his offer to get other Pebble Developers to help, was one of my most memorable Pebble development moments. Finally meeting all the developers and Pebble employees I had been chatting with for months before the retreat really made me feel like I was part of a community. ![](https://developer.repebble.com/assets/images/blog/070115-2014retreat.png) Jonathan (upper left) and his robot team at the 2014 Pebble Developer Retreat. The year following the 2013 Retreat was a busy one full of Pebble Firmware updates during the Pebble 2.0 rollout and app updates from developers. The [2014 Developer Retreat](https://developer.repebble.com/community/events/developer-retreat-2014.md) was approaching and I was so excited to get the chance to attend. It was amazing to return to see Pebble friends I had made the previous year, make new ones, and catch up with community developers old and new. It was at this retreat that I met Kat, the Event Engineer on the Developer Experience team at Pebble. I approached her and expressed my desire to take a little bit of Pebble home with me to spread in Ottawa. I had a feeling of loss when leaving the developer retreat the previous year and wanted to prevent that when I left this time. Pebble Meetups were already taking place in London and a couple of other locations, and I wanted to do the same in Ottawa. It was following this Retreat that Kat put out the official [Pebble Developer Meetup Playbook](https://docs.google.com/document/d/1PeYXRqw3VJ9UdFNMsBBbI2WtSm0QDzOmhhODyVbNy1Q) for satellite Pebble Developer Meetups. With this came the birth of the Pebble Rockstar. Pebble has become something very dear to me. I'm not just talking about the smartwatch I love to wear and develop apps for, I mean the community of developers and Pebble employees that add the human and emotional element to it all. I had been quite fortunate so far to meet these people and wanted to give back to the community. So, I decided to start up Pebble Developer Meetup Ottawa, [PebbleOTT](http://www.meetup.com/PebbleOTT/). PebbleOTT started off as just a few people having drinks and food in a pub listening to me talk about Pebble development, and has now grown into a bunch of people having drinks and food in a pub getting into some serious conversations about potential apps. Now, we’re even planning our first [Pebble Ottawa Hackathon](http://www.meetup.com/PebbleOTT/events/222726467/), headed by Matt Langlois from Ottawa University. These Meetups are like bringing a little part of the Developer Retreats back home, and help tide me over until the next one. The fact that I was able to get others interested enough to want to take part in Pebble Community Events makes this whole experience worth it. Having a little bit of Pebble at home helps keep me actively involved in the Pebble Developer community. ![](https://developer.repebble.com/assets/images/blog/070115-ottmeetup.jpeg) A few of PebbleOTT's April Meetup attendees sporting Music Boss beanies. Pebble Developer Meetups are a way to really immerse yourself in the Pebble developer community. They’re a way to discuss interests, meet new people, and share ideas for apps. For me, it has been a way to share my experience so far as a Pebble Developer and get more people on board. We meet regularly, have a few drinks, share some food, and are always thinking of what we can do next to grow our community. I wasn’t asked to organize Meetups, I volunteered to do it out of passion for what Pebble and Pebble development means to me. It’s more than compiling code and running it on a watch: it's a community of people who are passionate about improving people's lives by complementing the things they do throughout the day with small experiences. It's about friendship, sharing, a pat on the back when something works, and poking fun when the arm of your team’s Pebble controlled robot gets ripped off by another Pebble controlled robot. Being a Pebble Rockstar and organizing Meetups in my community is my way of staying in touch with the people and things that #MakeAwesomeHappen. Jonathan, Pebble Developer and Rockstar [#MusicBoss](https://play.google.com/store/apps/details?id=ca.rebootsramblings.musicboss), [#MusicTime](https://play.google.com/store/apps/details?id=ca.rebootsramblings.musictime), [#SimplyAlarm](https://play.google.com/store/apps/details?id=ca.rebootsramblings.simplyalarmholo)[rebootsramblings.ca](http://www.rebootsramblings.ca/) * * * --- # Pebble Timeline Challenge Week 8 Winner - Pokedex Challenge! Source: https://developer.repebble.com/blog/2015/06/25/timeline-challenge-week-eight/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 25 June 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/06/25/timeline-challenge-week-eight.md#comments) ![](https://developer.repebble.com/assets/images/blog/pokedex_collage.png) Pokemon fans are going to love the 8th winner of the Pebble Timeline Challenge, [Pokedex Challenge](https://apps.getpebble.com/en_US/application/5570838dddc5cc8060000012)! Have a blast catching the wild Pokemon as they appear in your timeline, and become a champion by completing the Pokedex. Mathew Reiss, an active Pebble developer and fan, created this detailed application. Users have multiple options, such as seeing each Pokemon's height and weight, navigating to their Trainer Card, and seeing their Pokedex progress. Here are some of the features Mathew used to make his app a reality. - [Timeline](https://developer.repebble.com/guides/pebble-timeline.md) to send pins to the user - [Persistent Storage](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") to store game information and status - [Paint.NET plugin](https://www.dropbox.com/s/qk9n3hrbiybh8k3/ConvertTo6Bit.dll?dl=0) to re-color Pokemon sprites. Below, Mathew tells us a bit about his background. ### Q&A with Mathew **What is your previous experience with Pebble development?** I actually started tinkering with the Pebble SDK about a year and a half ago - right after I ordered my Pebble Steel, I fired up CloudPebble and started working my way through tutorials. I finished my first watchface before my Steel even arrived. It didn't work at first, of course, but I was able to fix things quickly once I had hardware to test on. It's been a lot of fun developing for this platform. Every time a new feature comes out (which is often!), I try to build a simple watchface or app that utilizes it. That way, I have my own sample code to refer back to when I work on something bigger. It makes impulsive projects much easier, perhaps too easy! I also really like the simplicity of the screen resolution and color palette, since it makes development more accessible to those without years of experience in design or coding. **What is the backstory behind the Pokedex Challenge?** I've been a huge Pokemon fan since I played the original Blue version on a translucent purple Gameboy Color back in the day :P I actually worked on a game with my college roommate that turned our campus into its own Pokemon world, complete with Professors/Gym Leaders and pixelated school buildings. Pokemon-style games are such a simple but popular concept; it makes developing your own little indie game easier if you can re-use content and gameplay mechanics that have been proven time and again to work well. I've wanted to make a Pokemon related game for Pebble for a while now, but had a hard time restricting what I wanted to do into a game appropriate for the platform. Even this timeline-based concept started out too over the top as something I could never realistically do (kudos to Yuriy Galanter for entertaining the idea of collaborating with me on it, as ludicrously difficult as it would have been). Eventually, I was able to whittle the idea down into what I felt was the core experience, and built that. Not only did that mean I actually finished the game, but I think it came out much better too. I think things really clicked into place when I saw a notification based game for another platform called Lifeline. It was simple, it was passive, and it was really well done. It validated the idea of a passive but addicting gameplay experience. The idea of turning a standard Pokedex app into a passive game (waiting and responding to random events throughout the day rather than actively playing) appealed to me immediately. **Could you talk about the creation of Pokedex Challenge?** I was fortunate in that there were already several great Pokedex apps for SDK 2.0, so I didn't have to build everything from scratch. Once I forked the popular "Pokedex Kanto" app, the game development was really broken down into three phases. The first was building the basic JavaScript functionality to prove the game as a concept. This is where I had a lot of fun playing around with different ways of interacting with timeline pins (and which pin icons to use! I eventually settled on the Snapchat ghost as the closest one to a "monster" icon from the original games). Next was content creation. I went through and downloaded each sprite from Bulbapedia, then converted them all to 64 color palettes using a nifty plugin I wrote for Paint.NET. I also pulled data on type and species info, which form the coloring and text of each pin, respectively. The last phase of development was adding some simple but cool UI features like animated transitions and the trainer card, and testing/debugging. LOTS of testing/debugging :) Passive gameplay is great, but it was a new concept to me and one that required more patience than usual to properly test. "Cheating" in the sense that I increased pin frequency to a Pokemon every few seconds rather than every few hours helped, but required more constant attention than testing a minor feature in active gameplay would have. **Did you face any significant challenges? If so, would you like to expand upon them?** The game concept wasn't that technically difficult, but the timeline API is still very new and so there aren't a ton of great example apps - it was a lot of "learn as you go" development for me. I'm actually hoping to make the timeline-related code open-source at some point, so others don't have to go through the same learning curve. I also had some crucial help from the Pebble dev community - Gregoire Sage helped me with a persistent storage issue I couldn't have solved on my own (bitwise anything is above my pay grade), valorin caught a critical bug that was preventing some Android users from seeing pins (his experience from making timeline Tag was really valuable as a resource), and of course there's the many people who helped beta test and made it possible for me to catch simple mistakes in my code much faster. **What was your favorite part about making the app?** I really enjoyed pretty much all of the development process, barring a few frustrating moments here and there. If I had to pick a favorite moment, I'd say it was after my first successful run through of the full game. I caught the final Pokemon, received all the right end-of-game notifications, then kind of just sat back and was like, "woah, it's a real game now". **Any other projects that you'd like to talk about?** At this point, I'm actually taking a long overdue break from coding (even hobbies can be stressful if you overdo them!). I'll respond to bugs and make necessary updates still, and am definitely keeping an eye out to see who makes it onto the Pokedex Challenge Leaderboards, but for now I'm content to sit back and see what amazing games and apps other devs come up with. **Anything else you’d like to tell us?** Just how grateful I am to be part of this developer community! Pebble and the community do a great job making the platform feel accessible to those with little or no programming or design experience, and as a result you see a lot of great ideas come from people who might otherwise be too intimidated to dive in. It's been a blast. ![](https://developer.repebble.com/assets/images/blog/mathewreiss.png) ### 1 More Month! Time is running out! Make sure you don't miss out on [awesome prizes and swag](https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live.md#whats-in-it-for-you). Check out our past winners below to get inspired, and then get ready to **#makeawesomehappen**! ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) - Week 5: [Flights](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md) - Week 6: [Meetup](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md) - Week 7: [Greeney's Run](https://developer.repebble.com/blog/2015/06/18/timeline-challenge-week-seven.md) * * * --- # Pebble Timeline Challenge Week 7 Winner - Greeney's Run! Source: https://developer.repebble.com/blog/2015/06/18/timeline-challenge-week-seven/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 18 June 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/06/18/timeline-challenge-week-seven.md#comments) ![](https://developer.repebble.com/assets/images/blog/greeneys_collage.png) As the summer begins, we're all feeling the urge to relax a bit. With the 7th winner of the Pebble Timeline Challenge, [Greeney's Run](https://apps.getpebble.com/en_US/application/554f9adb4e604b9ed3000071), you can take a break by playing this colorful, exciting game! Rack up points while traveling through meadows, beaches, and more. Greeney's Run was created by Thomas Neff, an Austrian developer. Thomas' attention to detail is plainly visible in the game's features: there are over 17 different segments used to generate new levels, 3 backgrounds (daytime, evening, and night), and the ability to resume the game after reopening the app. Here are some resources he used to make those features a reality. - [Graphics API](https://developer.repebble.com/docs/c/Graphics.md "Graphics") to draw bitmaps - [Timer API](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer") to configure the main game loop to run at 30 FPS - [Persistant Storage API](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") to store the game state upon exiting as well as the all time high score - [Resource API](https://developer.repebble.com/docs/c/Foundation/Resources.md "Resources") to store the game segments Below, Thomas tells us a bit about his background. ### Q&A with Thomas **Tell us about your experience with Pebble development.** My first experience with Pebble Development was actually Greeney’s Run! Being my first Pebble app, I first spent a lot of time doing small little demos and experimenting with the Pebble APIs. It was a huge help that every API was well documented and I managed to get prototypes running pretty quickly. I was also really surprised by how easy it is to actually publish an app to the Pebble store! Integration of both Pebble Time and the original Pebble was also very easy. A huge part of my Pebble Development experience was [CloudPebble](https://cloudpebble.repebble.com/). While I had a development environment running using Ubuntu/Linux on my Laptop, it was really nice to use CloudPebble as a platform independent development tool for both my Desktop PC and my Laptop. **Why Greeney's Run?** I’ve been developing platforms as a hobby for about 9 years now. The first game I ever made was a pretty bad and inefficiently programmed platformer using ActionScript and Flash. Since then, there have been multiple iterations of platform games I made, and every time it was a new experience for me, because I either tried to improve physics code, or just tried to tackle a new development platform. My character of choice, the main character of Greeney’s Run, has always been a simple green square. It started as a green square because I didn’t really know how to do animation or proper art for my earlier games, but it grew on me and became somewhat like a signature character for me. Regarding the gameplay of Greeney’s Run, I’ve never done procedural or random generation of any kind in any of my games before, and it was something I really wanted to try out. Since the new Pebble Time has a color display, I decided to give it a shot, and that’s how I started developing Greeney’s Run! **Could you talk about the development of Greeney's Run a bit?** As everyone who is new to a development platform would probably do, I started by checking out the tutorials on the Pebble website. Coming from a background in mainly C and C++, I didn’t have a lot of trouble following the tutorials, and I managed to create a simple game rendering loop pretty quickly. From there, I implemented the background scrolling and physics engine, which I basically just ported over from one of my previous projects. My first few days of coding Greeney’s Run went on without many issues, but after I added more graphics and level segments, errors started to occur. I realized that I completely forgot I’m developing on a Pebble. I was using so much memory, as I was not used to developing on anything other than a PC for a long time. Always keeping this in mind has definitely helped the later process of development. Developing Greeney’s Run also involved lots of testing. I really wanted the gameplay to feel good and responsive, while providing a fun experience for everyone. I was also investing a lot of time thinking about the random level generation. Since Greeney’s Run is an endless runner type of game, I wanted each run to feel unique and special. For that purpose, I used level segments. These are handcrafted “challenges” which are then presented to the player in a random order. This hit a good compromise for me, I was able to randomly generate the order of level segments, while still providing fun and self-designed level segments. **What challenges did you face while developing?** The biggest challenge was definitely to fit everything I needed into memory. The scrolling backgrounds especially need a lot of memory, as they are quite big. Also keeping Greeney, the platform Tiles he can jump off of, as well as level segments in memory all at the same time proved to be quite difficult. I was always at the edge of memory usage for the Pebble Time, and I remember just tinkering with my background images in an image manipulation program for a few hours to save a few hundred bytes of memory. Even some things as the removal of a color or usage of a different dithering algorithm helped a lot. I really wanted to fit everything I designed into the game, and I barely managed to in the end. **Do you have a favorite moment from the development process?** My favorite part was developing the feature which allows the game to read the level segments from the flash storage. Since my available memory was pretty much reserved for graphics, I wanted to be able to just keep two level segments in memory at a time. This required me to dynamically load new segments every time needed. Before that, I was simple using static arrays to represent the level segments in code. While still a bit cumbersome, they allowed for human-readable editing of levels. After I switched the system to use level segments from the flash storage, and therefore binary data, I also wrote a little level segment editor in C# in order to have a visual tool to create my level segments with. It was really fun to see the level editor working in conjunction with Greeney’s Run after it was finished! I was also pretty exciting for me to work on some artwork again, as I have not done that for quite a while! **Any other projects that you'd like to talk about?** I don’t have any ideas at the moment and University is also keeping me pretty occupied currently. The Pebble development experience definitely had a positive influence on me though, and if I ever happen to stumble upon a cool idea, there is always the possibility for it to reach the Pebble! **Any words of wisdom for the Pebble Dev Community?** I’d like to encourage more hobby game developers to try out Pebble Development! I’d love to see awesome new games come out, especially for the Pebble Time. ![](https://developer.repebble.com/assets/images/blog/thomas_neff.png) ### Five Weeks to Go! Now that the Pebble Timeline Challenge is more than half over, there's no time like the present to begin developing! Check out our [guides](https://developer.repebble.com/guides.md), [documentation](https://developer.repebble.com/docs.md), and [examples](https://developer.repebble.com/examples.md) to **#makeawesomehappen**! ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) - Week 5: [Flights](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md) - Week 6: [Meetup](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md) * * * --- # Pebble Timeline Challenge Week 6 Winner - Meetup! Source: https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 12 June 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/06/12/timeline-challenge-week-six.md#comments) We've recently formed our 21st [Meetup group](http://pebble.meetup.com/), and are so excited that Pebblers around the world are meeting, sharing ideas, and making awesome happen! Now, with the 6th winner of the Pebble Timeline Challenge, you can keep track of the Pebble Meetup group as well as other local groups. View nearby Meetups and get event specific information such as the location and time: all in one informative timeline pin! ![](https://developer.repebble.com/assets/other/meetup.gif) Meetup's creator, Matt Langlois, is one of our most active developers, and has definitely made his mark on the Pebble community! With his contributions to Pebble.js, active participation in the Pebble Slack room, and organization of [Pebble hackathons](http://www.meetup.com/PebbleOTT/events/222726467/), he has proved that is he an extremely loyal developer. Here are some of the many features he used to create Meetup. - The [Pebble C SDK](https://developer.repebble.com/sdk.md) - [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) to communicate with timeline - [Timeline API](https://developer.repebble.com/guides/pebble-timeline.md) to design the UI - [PHPebbleTimeline](https://github.com/fletchto99/PHPebbleTimeline) to send events to the timeline from the server side - [Meetup API](http://www.meetup.com/meetup_api/) Node.js API to communicate with Meetup's open events stream - A custom designed PHP backend to communicate with the watch Read on to learn about Matt's story! ### Q&A with Matt **Tell us a bit about your experience as a Pebble developer.** Developing for the Pebble has been nothing less than an awesome experience! I got my red Pebble original as a Christmas present last year and as soon as I got it I wanted to start developing for the platform. Over the summer I started working on my first app, [uOttawa Card Manager](https://apps.getpebble.com/en_US/application/5468365c32e2d70d26000095), which would allow me to view my meal plan balance at the uOttawa. Unfortunately, at the time I did not know how to code in C so I went for their Pebble.js option instead and in a few hours I had myself a working app. I was quite pleased with the system to publish my app: I submitted it to the app store and within a few minutes I was able to share it with my friend who also had a Pebble. For a few months I was a lurker on the Pebble IRC channel but then the Pebble Slack community was introduced, allowing me to easily communicate with other developers and Pebble engineers. **What motivated Meetup?** A few months after the creation of my first Pebble app, an Ottawa Pebble Meetup group appeared. This gave me the idea to make Meetup. It started out quite simple, only listing the upcoming events for the group in Ottawa. I showed off this concept at our first Meetup and I received plenty of feedback and suggestions to improve it. After many late nights (one of which I accidentally committed my Meetup API key to github during, whoops!) the Meetup 1.0 was born. This first version contained the few core features that were required to make my app universal. **Tell us about the creation of Meetup.** Making Meetup as versatile as it is today was no easy task, but I wasn't about to let any challenges get in my way. I started off creating the second iteration, the one which was submitted to the Pebble Timeline Challenge, by writing a complex server backend in PHP to manage all of the various tasks. This API now runs all of the functionality the app offers today, from searching for a pebble event to looking for the nearest Photography group. All requests run through the API and get sent back to the watch in a clean format which can then be easily displayed. Introducing timeline functionality had its own set of hurdles because I needed to implement a way to send the user timeline pins if they have subscribed to a group. I achieved this through the creation of a Node script which maintains constant communication with Meetup.com and my Pin API, so when a new event is pushed I check which users are subscribed to the specific group or have already pinned it to their timeline, and then send it to their watches. During the creation of the timeline aspect I created PHPebbleTimeline which allows developers to easily access the timeline API through PHP. The final step, and hurdle, to meetup was to introduce colors. Unfortunately at the time of creation, Pebble.js didn't support colors and I didn't want to bug Meiguro with another feature requst for Pebble.js. So instead I forked it on GitHub and learned a bit of C to implement the color support in menus, which now exists in Pebble.js. It was a great learning experience, even though it didn't require too many changes. **What were some of the challenges you faced while developing?** The biggest hurdle in the creation of Meetup was finding time to improve upon the original app. I had so many ideas to add to the app, but it was the middle of exams so I had to put development on hold for a few weeks. As soon as exams finished I worked for about a week implementing timeline support so I could have it tested on real Pebble time instead of the emulator. It wasn't perfect when I had Cherie testing it at our Meetup, but basic event pins were working! I was quite pleased with the progress I made in the few months I had to work on the app. Now Meetup supports a wide range of features, including searching for non-pebble groups and events. **Do you have a favorite moment from when you were developing?** The best part about developing this app is that I have been able to explore multiple programming languages while still making something useful. Most of my life, and all of my school career I have been programming in Java. Through the creation of Meetup I have been able to use NodeJS, MySQL, PHP, Javascript and have them all interact with each other to create a simple yet functional app. Ultimately I hope to further expand my knowledge and write Meetup in Pebble's native language, C. **Are you working on any other projects that you'd like to talk about?** Other than improving my most recent app, [Devboard](https://apps.getpebble.com/en_US/application/556bbaf730850a5fab000089), I don't really have too many ideas for future apps. I'll be keeping an eye out on the Reddit page to see if anyone has any requests and probably just focus on improving the ones I've got out already. I'm also hoping to contribute more time to improving Pebble.js. **Any advice for other Pebble developers?** I'd like to encourage other developers not to give up. I was planning on stopping creation of Meetup but in the long run it's paid off. I'm glad so many people seem to enjoy the app! If you're ever looking for some development help you can find me along with many other developers in the Slack group! ![](https://developer.repebble.com/assets/images/blog/mattlanglois.png) ### Six Weeks to Go! Itching to become a Pebble developer? It's easy! Fire up [CloudPebble](https://cloudpebble.net/), join our amazing online community, and #makeawesomehappen. Keep submitting your work to the Pebble Timeline Challenge: we have 6 more weeks of awesome prizes, swag, and opportunities! ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) - Week 5: [Flights](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md) * * * --- # Pebble Timeline Challenge Week 5 Winner - Flights Source: https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 05 June 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/06/05/timeline-challenge-week-five.md#comments) ![](https://developer.repebble.com/assets/images/blog/flightsbanner.png) We've all been there at some point: you've just sprinted through the airport, rushed through security, and ended up breathless at your gate just to find out that your flight has been delayed for two hours. Well, with [Flights](https://apps.getpebble.com/en_US/application/555a8efcae067d58f800004d) on your wrist, you'll never miss another flight notification again! Get real time notifications about your flight status right on your wrist: just last week, one of us from Team Pebble enjoyed live updates about a delayed flight. Flights informs you of delays, terminal and gate numbers, baggage claim location, local arrival time, and much more! After you connect the app with [TripIt](https://www.tripit.com/) or add your individual flights manually, sit back and let the app figure out the details for you. Ronny Carr devoted his last few weeks to this app, and was able to use a variety of Pebble features and external APIs in his application. - The [Pebble C SDK](https://developer.repebble.com/sdk.md) - [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) for timeline integration - [Animations](https://developer.repebble.com/guides/graphics-and-animations/animations.md) - [Persistent storage](https://developer.repebble.com/guides/events-and-services/persistent-storage.md) for instant launches - [Timeline app Launch Reason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_TIMELINE_ACTION "APP\_LAUNCH\_TIMELINE\_ACTION") - The [Kayak](http://www.kayak.com/) API Below, Ronny tells us a little bit about himself, and why he decided to join the Pebble developer community. ### Q&A with Ronny **Why did you decide to become a Pebble developer?** I bought my first Pebble just this past November, opted for the Steel. Immediately fell in love with every aspect of the experience. It’s hard to ignore the lively developer community that surrounds the Pebble. As a developer myself, the interest was pretty high in creating my own watch face. Selenia was my first go at programming in C, and to my delight, a success! After that, I had the confidence to start working on an app to keep tabs on the status of my SmartThings controlled apartment. Dwelling came to fruition within a couple of days and only fueled the development fire further. Flights was started the next day. **Why Flights?** I wanted to take full advantage of the timeline with my next app. Honestly, I had looked through the list of timeline icons about 100 times and the cute little airplane kept jumping out at me. I knew this was one of the most obvious uses of the timeline, and really wanted to bring it to life. I love traveling and I love data, win win. **Could you tell us a little bit about the creation of Flights?** Flights has been a focus for a couple of weeks now. A few all nighters, and maybe an hour or two at the office ;). At first the challenge was finding a data source, basically two exist and they are not free. A major focus from the beginning was to limit the API hits as much as possible by storing the flight data and updating it on a schedule. I worked on one small aspect at a time, nothing was connected and certainly didn’t work. Slowly connecting the pieces together and building out the data was the most involved portion. With only a limited idea of what I wanted out of the app, most decisions were made while coding. Every corner rounded pretty much led to another "oh yeah, that" kind of moment. The most rewarding point during development was the first time I sent a pin to my timeline with real flight data. I couldn’t stop smiling. **What were some of the challenges you faced during the development process?** Developing Flights was, a challenge to say the least. One of the biggest differences in personal projects and work projects, there’s no team of 10+ people working on all the different aspects of development. As a developer it’s sometimes hard to stand back and see the larger picture of a project, which is why we are handed PSDs and specs. **Is there anything else you’d like to share?** I’ve continued making improvements to Flights with no plans to stop. I’ve got a few great things in the works and am excited to get them out for everyone to enjoy. A full Flights app is also planned, and is in early development. With a great set of tools, an amazing community, and incredible support, I can only look forward to my next project for the Pebble. ### Seven Weeks Left! With a little under 2 months left in the challenge, we encourage you to dive right in. Take a look at our previous winners below, fire up [CloudPebble](https://cloudpebble.repebble.com/), and show us what you've got! We've got a fabulous set of prizes waiting for you: don't miss out! ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) - Week 4: [Pushbutton](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md) * * * --- # Pebble Timeline Challenge Week 4 Winner - Pushbutton Source: https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 29 May 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/05/29/timeline-challenge-week-four.md#comments) ![](https://developer.repebble.com/assets/images/blog/pushbuttonbanner.png) The core goal of [IFTTT](https://ifttt.com/) (If This, Then That) is to use the Internet to simplify your life, and there is no better device to help with that goal than your Pebble! [Pushbutton](https://apps.getpebble.com/en_US/application/556111e92ff3b5887c000122), the 4th winner of the Pebble Timeline Challenge, works with any IFTTT Do Button action to help you create shortcuts for your daily life. **Notice** _Update 9th June 2015_ The Pushbutton app is unfortunately no longer available in the Pebble appstore. The options are truly endless: you can set up daily weather reports, create a way to find your lost phone, put a notification on your timeline everytime a stock increases, etc. One of our [developers](https://twitter.com/YGalanter/status/603339339658035200) already created a hack to use his Pebble and the IFTTT Gmail channel to make a personal voice note taker. ![](https://developer.repebble.com/assets/images/blog/dobutton_options.png) Pushbutton was the brainchild of Collin Fair, a Canadian developer and current University of Waterloo student. Here are some of the Pebble features and external resources he used while developing the app. - [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) for IFTTT API magic, timeline setup, geolocation - [Flask](http://flask.pocoo.org/) server for login page, timeline schedule management & pin pushing - [Animations](https://developer.repebble.com/guides/graphics-and-animations/animations.md) - [Persistent storage](https://developer.repebble.com/guides/events-and-services/persistent-storage.md) for instant launches - [Basalt Compatible PNG](https://developer.repebble.com/docs/c/Foundation/Resources/File_Formats/PNG8_File_Format.md) support for channel icons - [Timeline app launch\_args](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md#APP_LAUNCH_TIMELINE_ACTION "APP\_LAUNCH\_TIMELINE\_ACTION") for one-click action triggering Read on for a look into Collin's history as a Pebble Developer! ### Q&A with Collin **Do you have a background as a Pebble developer?** Strangely enough, my first experience developing for Pebble was during an internship on the firmware team - I was working behind the scenes on PebbleOS before I had written my first app! But, it wasn't long till I was drawn in. By far, my favourite part of developing for Pebble is pushing the limits of the platform, digging into the internals of the SDK and OS to create the perfect experience. **What inspired you to develop Pushbutton?** I'm a big fan of the idea of IFTTT - not to steal their marketing line, but it unlocks the true power of the Internet, no programming required. Having that power on your wrist just makes sense. Many actions are things that people do every day, like emailing someone that they're on their way home: a perfect use for the new timeline. And, as people might know, IFTTT support was promised in the original Kickstarter back in 2012 - so I thought it was about time that promise was fulfilled. **Could you tell us a little bit about the creation of Pushbutton?** The original design placed a much bigger emphasis on the icon of each action - after all, why waste that colour screen on boring text? But this ran into issues: downloading the large icons over AppMessage, even using the new Basalt PNG support, wasn't particularly fast. Furthermore, Pebble UX is all about "glanceability" (if that's a word), and if you had more than one SMS or GMail action you'd be stuck squinting at the tiny text below the uselessly large icon. So, I took advantage of the new colour screen to make the action name front and centre, still backed by the familiar Twitter blue or Evernote green, solving both problems at once. Of course, since its release I've had many great suggestions to improve the app further, so I'm sure I'll keep tweaking things. It's great to see the clever ideas people have come up with using the app in the first five days alone! **Any favorite moments while making the app?** It's a small thing, but the first time I hit the button on my Pebble and saw something actually happen via IFTTT. It wasn't until quite late in development that I hooked up the real button-pushing magic, so seeing the final piece fall into place was very satisfying. **Did you face any significant challenges during the development process?** The Pebble C SDK does a good job of hiding the trickier parts of embedded development, but there are still rough edges to be found. For instance, complicated animations can slow down AppMessage transmission - so I had to be careful that I wasn't sacrificing an efficient user experience for superficial (if cool-looking) animations. **Is there anything else you’d like to share?** I'm looking forward to see how everyone else puts the timeline to work - it's completely changed how I think about my Pebble. ![](https://developer.repebble.com/assets/images/blog/collinfair-pushbutton.png) ### Two Months to Go! Developers, you have 8 weeks left to secure your spot at the Developer Retreat! Don't give up this chance to win awesome prizes and meet the Pebble team. If you're still stuck about what to make, check our some of our [examples](https://developer.repebble.com/examples.md) and explore our [timeline guides](https://developer.repebble.com/guides/pebble-timeline.md) to give you that first burst of inspiration. We're so excited for you to #makeawesomehappen! ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) - Week 3: [Leaf](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md) * * * --- # Pebble Timeline Challenge Week 3 Winner - Leaf Source: https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 22 May 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/05/22/timeline-challenge-week-three.md#comments) ![](https://developer.repebble.com/assets/images/blog/leafbanner.png) With the rise of the Internet of Things (IoT), the number IoT products has increased almost exponentially. Slowly, connections between these devices are also being forged. [Leaf](https://apps.getpebble.com/en_US/application/52ccd42551a80d792600002c), the 3rd winner of the Pebble Timeline Challenge, falls into this latter category: the application allows your Pebble to act as a remote control for your [Nest](https://nest.com/thermostat/life-with-nest-thermostat/) thermostat from anywhere in the world! Want to come back to a toasty house after a winter vacation? Just bump up the temperature a few degrees while on your drive back from the airport. Are your kids a little too hot at home during the summer? Surreptitiously lower the temperature from your wrist while in a meeting. The Leaf application on your Pebble integrates with the Nest thermostat to allow you to adjust the temperature and manage away, fan, and operating modes. ![](https://developer.repebble.com/assets/other/leafgif.gif) Chris Nucci, a Florida based software engineer, was the mastermind behind this application, and his attention to detail made this app excel. Here are some of the features he used while developing the app. - The [Pebble C SDK](https://developer.repebble.com/sdk.md) - [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) - [Internationalization](https://developer.repebble.com/docs/c/Foundation/Internationalization.md "Internationalization") - [Logging](https://developer.repebble.com/docs/c/Foundation/Logging.md "Logging"), [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md "Clicks"), [Vibes](https://developer.repebble.com/docs/c/User_Interface/Vibes.md "Vibes"), [Timer](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer"), [Animations](https://developer.repebble.com/guides/graphics-and-animations/animations.md), [Graphics](https://developer.repebble.com/guides/graphics-and-animations.md) and many other APIs! Chris shared his story with us: read on to learn about his experiences with Pebble development. ### Q&A with Chris **Could you tell us a bit with your experience as a Pebble developer?** I’m a total believer in the open platform model. I feel that it benefits both the platform and user base more than a closed platform ever could. Pebble has it nailed in all aspects: the SDK, documentation, CloudPebble, forums, community libraries and resources, beta programs, and last but not least, the close, personal support of their developers. **What spurred you to create Leaf?** I like to joke that I write software to enable me to be lazy. While that doesn’t exactly hold up since I probably only save a few milliseconds of my life for every hour I spend developing, the Leaf app is the ultimate example of this. In a nutshell, not only did I not want to have to get up from the couch to adjust the thermostat, I didn’t even want to sit up to reach for my phone on the coffee table. So, it seemed like the Pebble was the perfect place for it. I wasn’t the first to come up with this idea though. There was an early version written with version 1 of the SDK, but it required an iOS companion app. When SDK 2 came out and included the JavaScript framework, I knew I could make a more universal version. This is actually my first Pebble app. I skipped the obligatory “hello world” and went straight for it! **Could you tell us a little bit about the development of Leaf?** The initial versions of Leaf were available before the official Nest API was released, so it actually used an API that someone else reverse-engineered to access data and control the thermostats. It eventually worked alright, but it initially took a lot of trial and error to figure out the correct commands to send. Things really went into high gear once the official API came out and Thomas Sarlandie contacted me to help foster a relationship directly with Nest. I switched to using OAuth2 and a Firebase JS client to handle communication with Nest and redesigned the UI for the third time to be compliant with Nest’s design guidelines. **What was your favorite part about making the app?** My favorite part was and still is finding new features to add. I love looking at APIs, imagining the possibilities, and bringing them to life. It’s all worth it when I get the random email from a user stating how much he or she likes the app. **Were there any significant challenges you faced?** Generally, the most difficult thing is managing memory and being efficient with data, but I enjoy that challenge. When the app closes and the log window says there are 12 bytes left allocated, you can be sure I’m diving back in to find them. **Are you working on any other projects that you'd like to talk about?** I’m currently working on adding Nest Protect support to Leaf and that should be ready by the time everyone starts getting their Pebble Times. Also, I’ve started working on another home automation app for the SmartThings platform. Maybe I’ll save some more milliseconds! **Anything else you’d like to tell us?** Just thank you, Pebble! And thanks to the other developers out there doing amazing things that we get to take advantage of! ![](https://developer.repebble.com/assets/images/blog/chrisnucci-leaf.png) ### 9 More Weeks! With over two months left in the challenge, there is still plenty of time to participate! If you already have an idea, open up [CloudPebble](https://cloudpebble.repebble.com/) and have some fun. If you don't know what to work on yet, don't worry! Browse our [timeline guide](https://developer.repebble.com/guides/pebble-timeline.md), check out the [examples](https://developer.repebble.com/examples.md), and have fun exploring. As always, if you have any questions, use our [contact form](https://developer.repebble.com/contact.md) to get in touch with us. ### Past Winners - Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) - Week 2: [FitCat](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md) * * * --- # Tips and Tricks - Platform-specific C File Set Source: https://developer.repebble.com/blog/2015/05/19/tips-and-tricks-platform-specific-c-file-sets/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 19 May 2015 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2015/05/19/tips-and-tricks-platform-specific-c-file-sets.md#comments) In [the last Tips and Tricks blog post](https://developer.repebble.com/blog/2015/05/13/tips-and-tricks-transparent-images.md) we looked at drawing transparent images on both the Aplite and Basalt platforms. This time around, we will look at a `wscript` modification that can allow you to build a Pebble project when you have two completely separate sets of C source files; one set for Aplite, and another for Basalt. Note: This technique can be applied only to local SDK projects, where access to `wscript` is available. This means that it cannot be used in CloudPebble projects. > Update 05/20/15: There was an error in the JS code sample given at the end of this post, which has now been corrected. ## The Preprocessor Approach In the 3.0 Migration Guide we recommend using preprocessor directives such as `PBL_PLATFORM_APLITE` and `PBL_PLATFORM_BASALT` to mark code to be compiled only on that particular platform. This helps avoid the need to maintain two separate projects for one app, which is especially convenient when migrating a 2.x app to the Basalt platform. ``` #ifdef PBL\_PLATFORM\_APLITE// Aligns under the status barlayer\_set\_frame(s\_layer, GRect(0, 0, 144, 68));#elif PBL\_PLATFORM\_BASALT// Preserve alignment to status bar on Aplitelayer\_set\_frame(s\_layer, GRect(0, STATUS\_BAR\_LAYER\_HEIGHT, 144, 68));#endif ``` This is a good solution for small blocks of conditional code, but some developers may find that complicated conditional code can soon become more `#ifdef...#elif...#endif` than actual code itself! ## The Modified Wscript Approach In these situations you may find it preferable to use a different approach. Instead of modifying your app code to use preprocessor statements whenever a platform-specific value is needed, you can modify your project's `wscript` file to limit each compilation pass to a certain folder of source files. By default, you will probably have a project with this file structure: ``` my_project resources images banner~bw.png banner~color.png src main.c util.h util.c appinfo.json wscript ``` In this scenario, the `wscript` dictates that _any_ `.c` files found in `src` will be compiled for both platforms. To use a different set of source files for each platform during compilation, modify the lines with the `**` wildcard (within the `for` loop) to point to a folder within `src` where the platform- specific files are then located: ``` forpinctx.env.TARGET\_PLATFORMS:ctx.set\_env(ctx.all\_envs[p])ctx.set\_group(ctx.env.PLATFORM\_NAME)app\_elf='{}/pebble-app.elf'.format(ctx.env.BUILD\_DIR)# MODIFY THIS LINE!# E.g.: When 'p' == 'aplite', look in 'src/aplite/'ctx.pbl\_program(source=ctx.path.ant\_glob('src/{}/\*\*/\*.c'.format(p)),target=app\_elf)ifbuild\_worker:worker\_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD\_DIR)binaries.append({'platform':p,'app\_elf':app\_elf,'worker\_elf':worker\_elf})# MODIFY THIS LINE!# Also modify this line to look for platform-specific C files in `worker_src`ctx.pbl\_worker(source=ctx.path.ant\_glob('worker\_src/{}/\*\*/\*.c'.format(p)),target=worker\_elf)else:binaries.append({'platform':p,'app\_elf':app\_elf}) ``` With this newly modified `wscript`, we must re-organise our `src` folder to match the new search pattern. This allows us to maintain two separate sets of source files, each free of any excessive `#ifdef` pollution. ``` my_project resources images banner~bw.png banner~color.png src aplite main.c util.h util.c basalt main.c util.h util.c appinfo.json wscript ``` ## Sharing Files Between Platforms Using the modified wscript approach as shown above still requires any files that are used on both platforms to be included twice: in the respective folder. You may wish to reduce this clutter by moving any platform-agnostic files that both platforms use to a `common` folder inside `src`. You project might now look like this: ``` my_project resources images banner~bw.png banner~color.png src aplite main.c basalt main.c common util.h util.c appinfo.json wscript ``` To tell the SDK to look in this extra folder during compilation of each platform, further modify the two lines calling `ctx.pbl_program()` to include the `common` folder in the array of paths passed to [`ant_glob()`](https://waf.io/book/#_general_usage). This is shown in the code snipped below, with unchanged lines ommitted for brevity: ``` # Additionally modified to include the 'common' folderctx.pbl\_program(source=ctx.path.ant\_glob(['src/{}/\*\*/\*.c'.format(p),'src/common/\*\*/\*.c']),target=app\_elf)/\*Othercode\*/ifbuild\_worker:# Also modify this line to look for common files in '/worker\_src/common/'ctx.pbl\_worker(source=ctx.path.ant\_glob(['worker\_src/{}/\*\*/\*.c'.format(p),'worker\_src/common/\*\*/\*.c']),target=worker\_elf)else:/\*Othercode\*/ ``` ## Important Notes While this new `wscript` allows us to keep our source files for each platform separated entirely, there are a couple of important limitations to take into account when using this method (without any further modification): - There can still be only _one_ JavaScript file, in `src/js/pebble-js-app.js`. You can simulate two JS files using a platform check: ``` Pebble.addEventListener('ready', function() {if(Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo().platform === 'basalt') {// This is the Basalt platformconsole.log('PebbleKit JS ready on Basalt!');} else {// This is the Aplite platformconsole.log('PebbleKit JS ready on Aplite!');}}); ``` - Each binary can be bundled with only the app resources required for that specific platform. To learn how to package app resources with only a certain platform, read the [_Platform-specific Resources_](https://developer.repebble.com/guides/app-resources/platform-specific.md) guide. ## Conclusion With this modification to a Pebble project's `wscript`, developers now have two options when it comes to diverging their app code for Aplite- and Basalt- specific features **without** the need to maintain two completely separate projects. You can see a simple example project that ses all these techniques over at [`pebble-examples/multi-platform-wscript`](https://github.com/pebble-examples/multi-platform-wscript). * * * --- # Pebble Timeline Challenge Week 2 Winner - FitCat Source: https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 17 May 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/05/17/timeline-challenge-week-two.md#comments) ![](https://developer.repebble.com/assets/images/blog/fitcatbanner.png) Has anyone been missing their Tamagotchi pet lately? If so, your prayers have been answered! Meet [FitCat](https://apps.getpebble.com/applications/552f63b406bedce1700000bb): the “funniest way to live an active life.” FitCat is a watchapp that combines fitness tracking and a virtual pet to spur you to lead an active lifestyle. Gnocchi, your pet cat, lives his life the way you do: when you're walking, he's walking, and when you're relaxing, he's relaxing. Being active is rewarded in Gnocchi's world: physical activity earns you money that can then be used to buy gifts and toys for Gnocchi. The healthier your lifestyle is, the happier your pet is! This watchapp's range of features impressed us immensely, and set this app apart from the rest. For example, there are day and night environments for Gnocchi, a mini game inside the app itself, and different animations for Gnocchi's behavior. If you want to gamify your fitness tracking, you can! You can set a daily step goal, set timeline pins to notify you about your progress, and see your fitness acheivements. ![](https://developer.repebble.com/assets/images/blog/fitcatpicture.png) FitCat was created by [Paul Bouillon](https://twitter.com/paulkubgames), a French software engineer. Paul utlized a number of Pebble features: - The [Pebble C SDK](https://developer.repebble.com/sdk.md) - [PebbleKit JS](https://developer.repebble.com/docs/pebblekit-js.md) - [Timeline](https://developer.repebble.com/guides/pebble-timeline.md) pins to inform user about daily step acheivements. - [Background Worker API](https://developer.repebble.com/guides/events-and-services/background-worker.md) for background step counting and opening FitCat when a step goal has been reached. - The [Persistent Storage API](https://developer.repebble.com/guides/events-and-services/persistent-storage.md) to store the fitness data. - [Launch Reason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md "Launch Reason"), [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md "Clicks"), [Vibes](https://developer.repebble.com/docs/c/User_Interface/Vibes.md "Vibes"), and many other APIs! To learn about the creation of FitCat, we interviewed Paul. Read on to learn more! ### Q&A with Paul **Was this your first experience as a Pebble Developer?** FitCat is the second app I created as a Pebble Developer. I developed a first app called [Match’em](https://apps.getpebble.com/applications/5433de4b79b9e561ea00002b) when I received my Pebble Steel last year. It is a simple game that I made while learning how to use the Pebble SDK. Then, when the Pebble Time was announced, I got motivated to develop something more ambitious: FitCat. **Could you tell us a little bit about the story behind FitCat?** It started after two of my relatives got activity trackers. As I was looking at them I thought it was a really good idea to have a device that tracks your daily activity. I then remembered those small devices with virtual pets I used to love as a kid, and it made me want to create something that could combine both: an activity tracker that would be fun to use. Since everyone loves cats, this is how FitCat came up. **Could you tell us a bit about the creation of FitCat?** I wanted to do something that people would enjoy using as much as I would. So at first, there had to be a background activity tracker, and cool environments and things to do for Gnocchi, the cat. But the more I worked on it, the more I wanted to improve it, by adding features such as timeline notifications or day and night variations when the cat is outside… I still have a lot of ideas in mind, be prepared to see cool new features in the next updates! **What was your favorite part of making the app?** I really enjoyed developing FitCat. My favorite part about making the app would be when I was working on how to draw the environment and the different animations of the cat. I was really excited when I first saw the cat walking in front of the environment, even if it was on the emulator. When I am developing an app, I find it really cool to see how the thing I originally had in mind turns into on the screen. **Do you have any other ideas that you're planning on exploring in the future?** Yes, I do. The only thing I can say right now is that it will be a game that will take full advantage of the Pebble Time’s color screen. I have not begun its development but it should come soon. I also plan to update my first game, Match’em, with new features. ### 10 Weeks to Go If you're feeling inspired, make your own Pebble project! Get started with our [guides](https://developer.repebble.com/guides.md) and [documentation](https://developer.repebble.com/docs.md) online, and make sure to submit your application to the [Pebble Timeline Challenge](https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live.md). We can't wait to see what you create! ### Past Winners Week 1: [Battery+](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md) * * * --- # Tips and Tricks - Transparent Images Source: https://developer.repebble.com/blog/2015/05/13/tips-and-tricks-transparent-images/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 13 May 2015 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2015/05/13/tips-and-tricks-transparent-images.md#comments) Ever wondered how to draw transparent images in a Pebble app? This post will walk you through the process. In this post, we'll be using a sample image with a transparency component, shown below: [![](https://developer.repebble.com/assets/images/blog/tips-transparency-globe.png)](https://developer.repebble.com/assets/images/blog/tips-transparency-globe.png) When adding your project resource, ensure you set its ‘type’ correctly. On CloudPebble, this is done when uploading the resource and choosing the 'PNG' type. In the SDK, this is done with the `png` `type` in the project's `appinfo.json`. ``` "media": [{"type": "png","name": "GLOBE","file": "globe.png"}] ``` This will create a resource ID to use in code: ``` RESOURCE_ID_GLOBE ``` Simply create a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") with this resource and draw the image with [`GCompOpSet`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOpSet "GCompOpSet") as the compositing mode: ``` static GBitmap \*s\_bitmap;static Layer \*s\_canvas\_layer; ``` ``` static void window\_load(Window \*window) {Layer \*window\_layer = window\_get\_root\_layer(window);// Create GBitmaps\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_GLOBE);// Create canvas Layers\_canvas\_layer = layer\_create(layer\_get\_bounds(window\_layer));layer\_set\_update\_proc(s\_canvas\_layer, layer\_update\_proc);layer\_add\_child(window\_layer, s\_canvas\_layer);} ``` ``` static void layer\_update\_proc(Layer \*layer, GContext \*ctx) {// Draw the image with the correct compositing modegraphics\_context\_set\_compositing\_mode(ctx, GCompOpSet);graphics\_draw\_bitmap\_in\_rect(ctx, s\_bitmap, gbitmap\_get\_bounds(s\_bitmap));} ``` When drawing on a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") underneath `s_canvas_layer`, the result looks like this: ![result-aplite](https://developer.repebble.com/assets/images/blog/tips-result-aplite.png) See a full demo of this technique on the [pebble-examples GitHub repo](https://github.com/pebble-examples/feature-image-transparent). Job done! Any transparent pixels in the original image will be drawn as clear, leaving the color beneath unaffected. Read the [Image Resources](https://developer.repebble.com/guides/app-resources.md) guide to learn more about transparent PNGs. ## Conclusion So there you have it. Using these examples you can easily implement transparency on all Pebble platforms. To learn more, read the [`GCompOp`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp "GCompOp") documentation or the [`pebble-examples/feature-image-transparent`](https://github.com/pebble-examples/feature-image-transparent) SDK example. * * * --- # Pebble Timeline Challenge Week 1 Winner - Battery+ Source: https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 08 May 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/05/08/timeline-challenge-week-1.md#comments) ![](https://developer.repebble.com/assets/images/blog/batteryplusbanner.png) On Monday, we announced the first winner of the Timeline Challenge: [Battery+](https://apps.getpebble.com/applications/55197642d6fa6f2633000092). Battery+ was created by [Eric Phillips](https://twitter.com/ericphillips0), a Pebble developer who many may know for his watch timer, Timer+. Twitter has been abuzz with compliments for Eric, and needless to say, we agree! Battery+ is a useful, beautifully designed app that gathers data to generate intelligent battery life predictions. When you first open the app, you are met with a detailed tutorial that describes how to use the app, as well as what it can do. Clicking through the lengthy animation can give you details about what the buttons are used for, what kind of data the app tracks, and the different ways you can view the data. If you click through the app, you can get more detailed information, such as the maximum life of your Pebble and how long it has lasted for. ![](https://developer.repebble.com/assets/other/batteryplustutorial.gif) Once the app begins to track your information, it will begin sending you timeline pins about when the next charge needs to be. ![](https://developer.repebble.com/assets/images/blog/batteryplustimeline.png) Eric made use of a variety of features to create his app, including the following: 1. The [Pebble C SDK](https://developer.repebble.com/sdk.md) 2. The [Background Worker API](https://developer.repebble.com/guides/events-and-services/background-worker.md) to collect battery data continuously 3. The [Persistent Storage API](https://developer.repebble.com/guides/events-and-services/persistent-storage.md) to store the collected data 4. [Timeline](https://developer.repebble.com/guides/pebble-timeline.md) pins to send "Charge by" data to the user 5. [PebbleKit JS](https://developer.repebble.com/blog/2013/11/21/Using-PebbleKit-JS-Configuration.md#what-now) to store the data on the phone or share on social media 6. [Launch Reason](https://developer.repebble.com/docs/c/Foundation/Launch_Reason.md "Launch Reason"), [Wakeup](https://developer.repebble.com/docs/c/Foundation/Wakeup.md "Wakeup"), [Clicks](https://developer.repebble.com/docs/c/User_Interface/Clicks.md "Clicks"), [Vibes](https://developer.repebble.com/docs/c/User_Interface/Vibes.md "Vibes"), and many other APIs! The attention to detail that Eric shows in this app is amazing. For example, when you scroll to the end of the graph of your battery life, there is a small animation and vibration that prevents you from going further. Features like these impressed us, as it highlighted the thought Eric put in to ensure that users had a seamless experience with his app. We talked to Eric to learn more about his journey as a Pebble Developer. Below, he details why he began Pebble development, the process of creating Battery+, and hints at some future projects. ### Q&A with Eric **Could you tell about your experience as a Pebble developer?** I love to program, and have always wanted to publish my programs for other people to use. However, most app stores required you to be over eighteen and pay a registration fee. Pebble's openness was very attractive, and I began programming for it soon after receiving my Pebble. Since then, it has been awesome developing. It is really cool to work with such a small screen, trying to find unique ways to display information, and since Pebble's store is still relatively young, there is so much potential to be able to write a popular app! **What is the story behind Battery+?** Battery+ actually came from another app I had previously made, "Battery Leak". The idea behind Battery Leak was to create an app with true background battery tracking, and it worked fine. However, as time progressed and I learned more, I kept thinking of better ways to do things, and new features to add. This is where Battery+ began. I wanted to remake Battery Leak, but in the process I wanted to have a larger data range (Battery Leak held one week) and I wanted to have intelligent battery life predictions. Rather than looking at your watch and seeing that you have 20% of your battery left, and then trying to figure out if that will make another day, I thought it would be so much better if your watch could just tell you how many days of battery it has left. This worked perfectly with the timeline concept, where I was able to insert a "Charge by" event, complete with a one day remaining alert and some details about the last charge. **Could you give us a few more details about the creation of Battery+?** I began Battery+ much like my other apps, just making something I would be happy using. However, after I completed the first version, I posted some screen-shots on social media to get other people's feedback. I was given many great ideas, and the "Stats" screen and ability to view the data on the phone came out of this. Later, I found a few beta testers (thanks!), and their feedback was a tremendous help in making Battery+ more useable. **What was your favorite part about making the app?** It's hard to say what my favorite part was... I like optimizing code, and it was a fun challenge to see how efficient I could make it, but I think my favorite part was designing the UI. I love making a nice interface, and then seeing what sorts of little things I can add to make it better. A couple examples of this would be if you zoom too far in or out in the graph, it gives a little "popping" animation and pulses the vibrator, kind of like gear teeth being stripped. Also, in the configuration page, the color theme changes based off what the current battery percentage is. **Do you have any other app ideas that you're planning on exploring in the future?** I have a few ideas, and I am always looking for suggestions! Right now, I am working on a sleep/activity tracking app. My goal is to create a simple but beautiful app, more similarto my Timer+ app than to Battery+ (and yes, it will have a "+" in its name). **Anything else you'd like to tell us?** I think the timeline challenge is a great idea, and I really look forward to seeing what other people come up with! ![](https://developer.repebble.com/assets/images/blog/eric-phillips.png) ### Future Steps If you'd like to participate in the timeline challenge, you have 11 more weeks to do so! Check out our [blog](https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live.md) about the Pebble Timeline Challenge, [FAQ](https://developer.repebble.com/blog/2015/05/07/timeline-challenge-faq.md) about the Timeline Challenge, as well as our [guides](https://developer.repebble.com/guides.md) and [documentation](https://developer.repebble.com/docs.md) to get started with programming for Pebble. We're so excited for you to #makeawesomehappen! * * * --- # Timeline Challenge - FAQ Source: https://developer.repebble.com/blog/2015/05/07/timeline-challenge-faq/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 07 May 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/05/07/timeline-challenge-faq.md#comments) ![](https://developer.repebble.com/assets/images/blog/timelinechallengefaq.png) We hope everyone is as excited about the [Timeline Challenge](https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live.md) as we are! Look out for details on our first winner, [Battery+](https://apps.getpebble.com/applications/55197642d6fa6f2633000092), in this week’s [developer newsletter](http://pbl.io/devnewsletter). Below, we’ve answered some of the questions that have been popping up about the challenge. If we’ve missed anything, fill out our [contact form](https://developer.repebble.com/contact.md) or tweet at [@PebbleDev](https://twitter.com/PebbleDev) to get in touch with us! ## Questions **Oh no, I didn’t win this week. Do I have to make a new app to be considered in the future?** Nope! As we pick each week’s winner, we’re going to be looking through **ALL** the apps that have been submitted. So, even if you submitted your app before the first winner was chosen, you’ll still be in the running as we pick our last winner! **Can I win multiple times?** No, you can’t win twice. Although you won't be able to win again, we encourage winners to keep hacking! We would love to see more exceptional apps, and there could be suprises for you in the future! **Do I have to use timeline?** No, it's not a requirement. However, we would like for your apps to integrate timeline. The timeline is such an exciting, unique way to interact with your users, and we love seeing developers take advantage of it! That being said, if you do make an awesome watchface or app, you'll definitely still be in the running! We're excited to see what you make. **Can I submit multiple apps?** Yes, you can submit as many apps as you want. However, as mentioned above, you will only be able to win the challenge once. **I’ve been improving my app for the last three weeks. Can I resubmit it?** Of course! We would love to see the improvements you’ve made. ## Looking to the Future If you’ve just heard about the Timeline Challenge, don’t worry! There’s plenty of time to get involved. Make sure you check out our [guides](https://developer.repebble.com/guides.md) and [documentation](https://developer.repebble.com/docs.md) online, boot up [CloudPebble](https://cloudpebble.net/), and get started! We can’t wait to see what the next 11 weeks hold! * * * --- # Timeline Spotlight - Hackawatch Source: https://developer.repebble.com/blog/2015/04/17/timeline-feature-hackawatch/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 17 April 2015 - [Niharika Bedekar](https://developer.repebble.com/blog/authors/niharika/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/04/17/timeline-feature-hackawatch.md#comments) ![](https://developer.repebble.com/assets/images/blog/hackawatch.png) Hackathons are packed with tutorials, tech talks, and demos, and it’s simple for important activities to sometimes slip through the cracks. However, thanks to [Hackawatch](http://challengepost.com/software/hackawatch), Pebblers will never miss an event at a hackathon again! Hackawatch is a Pebble application created at LA Hacks that allows hackathon organizers to push pins to hackers’ timelines to notify them about important events. 1. **Who** : 3 Arizona State University students: Jack Ketcham, Tim Kaing, and Tim Horng. 2. **What** : Hackawatch, a Pebble application that allows hackathon attendees to see important events during the hackathon on their timeline. 3. **Where** : LA Hacks 4. **When** : April 3rd- 5th 5. **Why** : The team is going to be organizing their own hackathon during the fall of 2015, Desert Hacks, and thought it would be a useful application to have in place during their event. 6. **How** : Using the [guides](https://developer.repebble.com/guides.md), [documentation](https://developer.repebble.com/docs.md), and [blog posts](https://developer.repebble.com/blog/2015/03/20/Getting-Started-With-Timeline.md) online! ## Q&A with the Hackawatch Team We recently talked to these first time Pebble hackers, and got their story. **How did you get started with your project?** When we started setting up the project and needed to learn more about developing on the Pebble, the first place we went to was Pebble's developer site, and we looked through the relevant documentation for Pebble C and PebbleKit JS, as well as the guide for the timeline. In addition, we found the Pebble examples on GitHub to be very helpful, both the regular Pebble C examples and the new timeline examples. **Why did you decide to use the timeline interface?** We decided we wanted to use the timeline interface because we felt it was exactly what we needed to solve our hackathon event feed problem for attendees, and also because we found out about it through all of us pre-ordering the Pebble Time. We thought it would be great to build something we would be using ourselves in just a couple months. Not to mention, we also thought it would be the best thing to hack on since it's one of the newest API features and something that all Pebble watches would have eventually anyway. **Which frameworks did you use for your project?** I used the native SDK emulator on my MacBook Pro to test out the app as we were developing it, and we used PebbleKit JS running on the user's phone to retrieve the user's timeline token as well as our own NodeJS API. Further, we would send HTTP requests to our API, sending along the user's timeline token and their preferences for what they wanted to subscribe to so we could use the [pebble-api-node module](https://www.npmjs.com/package/pebble-api) on our end. Also, when I was working on implementing the [pebble-api-node module](https://www.npmjs.com/package/pebble-api) in our backend, I also found some missing API functionality. At the hackathon, I was able to add the functionality (deleting user pins) with appropriate tests and submitted a pull request, which got merged in shortly after the event! **What was the hardest part about making the app?** I'd say the hardest part about developing the app was ramping up on the Pebble C library for creating the user interface on the Pebble, because it was a new API for us and none of us had any prior experience writing C. Again, to combat that, we looked at the available documentation and examples, and also got help from the Pebble Engineers that were on site throughout the hackathon (they were awesome!). **What was your favorite part about making the app?** I’d say the best part about developing this app would be gaining experience developing on Pebble's newest API. This is the next big move for the company and for the people that own these watches, and being able to be a part of that and show what we can make with the new functionality is really amazing. ## Future Steps Hackathon organizers, take note! The story hasn’t ended here for Hackawatch. The team is going to continue adding functionality to their app: soon, they want to have an RSVP action for certain activities or tech talks. Their ultimate goal is to make it as easy as possible for any other hackathon organizers to fork the project into their own repo and adapt it for their event. ## Inspired to Start Hacking on Pebble? Follow the Hackawatch team’s lead and hack your heart out! We have tons of [guides](https://developer.repebble.com/guides.md) and [documentation](https://developer.repebble.com/docs.md) online that are perfect for first time hackers. If you want to share your project, enter it into the [Timeline Challenge](https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live.md)! For the next twelve weeks, we’re going to be giving out free Pebble Times, swag, and prizes, so fire up [CloudPebble](https://cloudpebble.repebble.com/) and hack away! ## Want to Hang Out with Pebble? If you’re going to be in San Francisco next week, come to our [Meetup](http://www.meetup.com/PebbleSF/events/221717043/)! We’ll be talking specifically about timeline and all the cool features that come with it. Otherwise, keep an eye on our [Events](https://developer.repebble.com/community/events.md) page to figure out where we’ll be soon. We have a busy few months coming up, and want to see as many Pebblers as possible. We can’t wait to meet you! * * * --- # Deploying A Server For Your Timeline Enabled App With Heroku Source: https://developer.repebble.com/blog/2015/04/16/Deploying-A-Server-For-Your-Timeline-Enabled-App-With-Heroku/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 16 April 2015 - [Kirby Kohlmorgen](https://developer.repebble.com/blog/authors/kirby/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/04/16/Deploying-A-Server-For-Your-Timeline-Enabled-App-With-Heroku.md#comments) ![](https://developer.repebble.com/assets/images/blog/deploying-server-header.png) Pins are a new and intuitive way for your users to interact with your app via the Pebble timeline. In a previous [blog post](https://developer.repebble.com/blog/2015/03/20/Getting-Started-With-Timeline.md) I went through a complete walkthrough on how to get started with timeline. This post will focus on one single part of that process; how to deploy a server with [Heroku](https://www.heroku.com/), run jobs with [Scheduler](https://addons.heroku.com/scheduler), and how to build APIs that push pins with the [Express](https://www.npmjs.com/package/express) and [pebble-api](https://www.npmjs.com/package/pebble-api) node modules. ## Prerequisites There are few things we’ll need to set up before we can deploy our server - An account with [Heroku](https://signup.heroku.com/) - The [Heroku Toolbelt](https://toolbelt.heroku.com/) - [Node and npm](https://nodejs.org/download/) installed ## The Project This time around we'll discuss how to deploy a server to push pins as part of your app experience. We'll utilize [Heroku](https://www.heroku.com/) for this tutorial, because they make it super simple to deploy a server up to the cloud. Our server will run Node.js with the `Express` npm module to serve our HTTP endpoints. This allows us to utilize the [pebble-api](https://www.npmjs.com/package/pebble-api) npm module which makes it super easy to create pins and push them to our users. ## Why Do I Need My Own Server? With the addition of timeline to the Pebble SDK developers are now encouraged to enhance their applications by running their own servers that bridge communication between their users' Pebble and the timeline web API. ![timeline architecture](https://developer.repebble.com/assets/images/guides/3.0/timeline-architecture.png) This type of architecture allows you to do more for your users. Some of the things you can now do include: - Push pins to users without your app running - Push pins to all users - Store user data (like preferences) in the cloud > Of course you aren't required to use a server to talk to the timeline web API, you can make requests directly via PebbleKit JS. A great example of this is the [timeline-push-pin](https://github.com/pebble-examples/timeline-push-pin) app. This approach allows an app to push pins to just the specific user. You can also use it to push shared pins to topics, but this will require storing the app's production API key in the JS, which we advise against. ## Setup A Simple Webserver The following commands will create a folder, create a file called `server.js`, create a file called `package.json`, install the Express and pebble-api modules and add them to list of dependencies in `package.json`. ``` $ mkdir node $ cd node $ touch server.js $ npm init# all of the default options are fine$ npm install express pebble-api --save ``` In order to talk to the timeline web API, you need to obtain an API key from the [Developer Dashboard](https://dev-portal.getpebble.com/). Once you've obtained the API key you need to set it as an environment variable, so the `pebble-api` npm module can use it. ``` $ export PEBBLE\_TIMELINE\_API\_KEY=XXXXXXXXXXXXXXX # make sure to use your actual API key ``` Open `server.js` in your favorite editor and paste the following: ``` var express = require('express');// import the express modulevar Timeline = require('pebble-api');// create a new Timeline object with our API key passed as an environment variablevar timeline = new Timeline();var app = express(); // create an express instanceapp.set('port', (process.env.PORT || 5000)); // set the port on the instance// push a pin to :userToken from our webserviceapp.get('/senduserpin/:userToken', function(req, res) {var userToken = req.params.userToken;var pin = new Timeline.Pin({id: parseInt(Math.random() \* (999999 - 100000) + 100000).toString(), // random pin idtime: new Date(new Date().getTime() + 60\*60\*1000), // current date and time plus 1 hourlayout: new Timeline.Pin.Layout({type: Timeline.Pin.LayoutType.GENERIC\_PIN,tinyIcon: Timeline.Pin.Icon.PIN,title: 'Pin from Heroku!'})});timeline.sendUserPin(userToken, pin, function (err, body, resp) {if(err) {res.status(400);res.send(err);} else {res.send('Pin successfully pushed!');}});});// start the webservervar server = app.listen(app.get('port'), function () {console.log('Package Tracker server listening on port %s', app.get('port'));}); ``` When you run `node server.js` this creates a super simple webserver. When a request is made to `https://localhost:5000/senduserpin/` that will send a pin to that user's Pebble saying "Pin from Heroku!" You can test this locally by visiting [http://localhost:5000/senduserpin/yourtoken](http://localhost:5000/senduserpin/yourtoken) in your browser. > Make sure to replace "yourtoken" with your actual token in the above link! ## Commit, Push, Deploy From here we can initialize a `git` repository, and commit our code so that we push our code to Heroku. ``` $ git init $ git add . $ git commit -m 'First commit' ``` > I would suggest adding a `.gitignore` file that ignores the `node_modules` folder. [Here](https://www.gitignore.io/api/node) is a good one. Let's create a Heroku app and connect it to our repo. ``` $ heroku create ``` > Since we initalize our repo before we created our Heroku app, the Heroku Toolbelt will automatically add a git remote to Heroku's servers. Now we just need to push our app to Heroku! ``` $ git push heroku master ``` This will push our repo to Heroku, where they'll take care of configuring the server, running our Node.js application, etc. Before our server will work we need to set the timeline web API key environment variable on Heroku. ``` $ heroku config:set PEBBLE\_TIMELINE\_API\_KEY=XXXXXXXXXXXXXXX # make sure to use your actual API key ``` You can check out your server by running: ``` $ heroku open ``` This will take you somewhere like `https://.herokuapp.com`. Like in the local example, you can append `/senduserpin/yourtoken` to push a pin your Pebble. ## Pushing Nested Folders to Heroku Often you'll want to keep both your server and your Pebble app in the same folder. The best way to do this is to have two separate folders, one for the server and one for the Pebble app, with in another folder. A great example is the [Timeline TV Tracker](https://github.com/pebble-examples/timeline-tv-tracker). This poses a problem, because Heroku expects our application to be in the root of our git repo. To resolve this problem instead of running `git push heroku master` run ``` $ git subtree push --prefix heroku master ``` > You can read more about git-subtree by reading the official [documentation](https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt). ## Heroku Scheduler Sometimes your server will need to run some periodic task. For example the [Timeline Package Tracker](https://github.com/pebble-examples/timeline-package-tracker) checks to see if users have any new packages every 5 minutes. The Timeline Package Tracker accomplishes this by using [`setInterval`](https://github.com/pebble-examples/timeline-package-tracker/blob/master/server/server.js#L24). Unfortunately this will not work on Heroku, because Heroku goes to sleep after 60 minutes of inactivity. We can fix this by utilizing [Heroku Scheduler](https://devcenter.heroku.com/articles/scheduler) which lets us run abritrary jobs on a scheduled basis. We can create a small JavaScript file that we can run periodically. We'll need to put this in a local `bin` directory so that Heroku knows where to find it. ``` $ mkdir bin $ touch bin/pushTopicPins ``` In your favorite text editor put the following in `pushTopicPins`. ``` #!/usr/bin/env nodevar Timeline = require('pebble-api');function pushTopicPins() {// create a new Timeline object with our API key passed as an environment variablevar timeline = new Timeline();var pin = new Timeline.Pin({id: parseInt(Math.random() \* (999999 - 100000) + 100000).toString(), // random pin idtime: new Date(), // current date and timelayout: new Timeline.Pin.Layout({type: Timeline.Pin.LayoutType.GENERIC\_PIN,tinyIcon: Timeline.Pin.Icon.PIN,title: 'Another pin about a topic from Heroku!'})});timeline.sendSharedPin(['some\_topic'], pin, function (err, body, resp) {if(err) {console.error(err);}});}pushTopicPins(); ``` > In order to leverage the `pebble-api` npm module you need to set `PEBBLE_TIMELINE_API_KEY` with your timeline API key. Everytime this script is run it will push a pin to every user Add it to your git remote, push, and test it: ``` $ git add bin $ git commit -m 'Add pushTopicPins job'$ git push heroku master $ heroku run pushTopicPins ``` To schedule the job we first need to add the Scheduler addon: ``` $ heroku addons:add scheduler:standard ``` Then we can schedule the job to run through the dashboard: ``` $ heroku addons:open scheduler ``` Heroku makes it easy to see all of the logs for your app in one place. This includes information about each time a job is run. ``` $ heroku logs ``` > Note, Heroku is free until you exceed 750 dyno-hours per month. If you're running jobs that take a very long time you could potentially exceed that limit. For more information check out their [Usage & Billing](https://devcenter.heroku.com/articles/usage-and-billing) article. ## Conclusion This tutorial should help you hit the ground running when deploying your first server for timeline. I highly encourage you to try and deploy the [Timeline TV Tracker](https://github.com/pebble-examples/timeline-tv-tracker) and see how it all works together. Have questions or want to share something awesome you've made for Pebble? Contact at us on twitter [@pebbledev](https://twitter.com/pebbledev/) or [share your project](https://developer.repebble.com/contact.md). * * * --- # The Timeline Challenge is Live! Source: https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 14 April 2015 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/), [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/04/14/the-timeline-challenge-is-live.md#comments) ![](https://developer.repebble.com/assets/images/blog/timeline-challenge.png) In last week’s [timeline blog post](https://developer.repebble.com/blog/2015/04/10/it-is-time-to-put-your-ideas-on-our-timeline.md), we teased a new competition we’re running. This will be a challenge to see who can create the coolest Pebble applications that are optimized for the Pebble Time: we’re interested in seeing apps that use the timeline interface as well as other awesome watchfaces and Pebble apps in color. Winners can look forward to a Pebble Time, swag, and publicity in the Pebble developer community. The Timeline Challenge will feature 12 winners, one per week for 12 weeks. We’ll start announcing winners on May 4th, so you have plenty of time to develop and submit your apps before the first deadline! Don’t worry though, you can continue to work on your apps throughout the challenge and submit them after the first round of the challenge, but the early bird gets the first Pebble Times! ## What We're Looking For We will only consider apps that were created with the Pebble Time in mind, and would prefer to see seamless timeline integration that will add important functionality to Pebblers' lives. However, if you have an idea for an amazing color watchface or a unique watchapp, we're all eyes! In the coming weeks, we'll be releasing guidelines for UI components and UX best practices. The best apps will show that you have given thought to how your apps relate to the overall Pebble Time experience and will follow these guidelines. Stay tuned for updates on these guides via our [developer newsletter](http://pbl.io/devnewsletter) and make sure to [sign up](http://pbl.io/devnewsletter) if you aren't on our list! ## How to Enter the Challenge To be considered for the challenge, make sure you have [published your app](https://dev-portal.getpebble.com/) to the Pebble appstore and then fill out our [Google Form](http://goo.gl/forms/1pOhTRoxJq). Share your app with the world using #timelinechallenge to get beta testers and new Pebble Time users to download your app! We'll announce the winner every Monday for 12 weeks (May 4th for week 1), and contact the winner via email! If you don't win one week, don't lose hope! Your submission will roll over, and will be evaluated again with the next round of apps. However, you can only win once. If you do win, don't stop developing! We have some surprises in store for outstanding applications made by previous winners. ## What’s in It for You? Well, we have some fantastic prizes lined up: - An invitation to the 2015 Pebble Developer Retreat (including airfare and accommodation if needed) - A Pebble Time watch - A $500 Amazon gift card - A Pebble Swag Bag including PJs, Pebble Time shirt, and other goodies In addition, we’ll feature your app on the Pebble appstore and cover it on the Pebble Developer Blog. ## Getting Help with Timeline Need some help getting your app off the ground? If you haven’t already, make sure you check out [our recent blog post on getting started with timeline](https://developer.repebble.com/blog/2015/03/20/Getting-Started-With-Timeline.md), [our timeline guides](https://developer.repebble.com/guides/pebble-timeline.md), and example applications such as [Hello Timeline](https://github.com/pebble-examples/hello-timeline) and [Timeline TV Tracker](https://github.com/pebble-examples/timeline-tv-tracker). We’ll also be showcasing timeline at our next [Meetup](http://www.meetup.com/PebbleSF/events/221717043/), on April 23rd in San Francisco. At the event, we’ll be walking through how to create timeline apps as well as detailing many of the other awesome features of timeline. If you can’t make it to SF, join us virtually! We’ll be livestreaming the event: make sure you follow [PebbleDev](http://twitter.com/PebbleDev) or keep an eye on the [Meetup](http://www.meetup.com/PebbleSF/events/221717043/) page for the link when it goes live. We can’t wait to see all of your #timelinechallenge submissions! Please use our [contact form](https://developer.repebble.com/contact.md) if you have additional questions about the challenge! Love, Team Pebble * * * --- # It's Time to Put Your Ideas On Our Timeline! Source: https://developer.repebble.com/blog/2015/04/10/it-is-time-to-put-your-ideas-on-our-timeline/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 10 April 2015 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/), [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/04/10/it-is-time-to-put-your-ideas-on-our-timeline.md#comments) ![](https://developer.repebble.com/assets/images/blog/calling-header.png) For the past few weeks, we’ve been releasing updated elements of our new SDK. As many of you know, one of the most important facets of the Pebble Time is the **Pebble timeline**. The timeline is a core feature of the Pebble Time that allows users to see important events in their past, present, and future. The timeline creates a unique opportunity for both Pebblers and developers. Not only can users simplify their lives by tracking the important events in their days, but developers can also create applications that push pins directly to their users’ Pebbles. ## Great Uses of Timeline Timeline was envisioned to be a truly personal experience for users: they have the power to determine exactly what content goes on their timeline. Here are some potential ways Pebblers can use their timeline: - A mom can track her child’s flight itinerary. - A swimmer can check the times of all his races for the day. - A doctor can see which meetings she missed during her last surgery. - A student can check when he next has to take his medicine. - A tailgator can keep track of their favourite sports teams. The possibilities are endless, which is why we now want to turn to our amazing, creative community to see what you all can create. There are so many applications that we would love to see materialize, and the ideas below are barely the tip of the iceberg. - Sports tracking: upcoming matches, live scores, notifications on goals, etc - Transit (Bus/Train/Subway/other) times for your city - Reminder services and integration with to-do list applications - TV reminders: being able to select your favorites so that you’ll be notified when the program will air. - Flight status trackers - Weather alerts: snow alert/ice alert/pollen alerts/extreme weather notifications - Payment reminders - Coupon/discount notifications - Software/website performance notifications ## How to Get Started With Timeline These apps and others like them would be widely used, and we encourage you to start developing. For a detailed tutorial on how to write your first timeline application, check out this [blog post](https://developer.repebble.com/blog/2015/03/20/Getting-Started-With-Timeline.md). You can also peruse our [timeline guides](https://developer.repebble.com/guides/pebble-timeline.md), as well as a few open source examples such as [Hello Timeline](https://github.com/pebble-examples/hello-timeline) and [Timeline TV Tracker](https://github.com/pebble-examples/timeline-tv-tracker). If you’d like some extra guidance before jumping in and developing your own app, you’re in luck! Our next [meetup](http://pebble.meetup.com/), which is on April 23rd in San Francisco, will be focused exclusively on timeline. We’ll be giving live demos on how to create timeline apps, how to utilize timeline pin actions, and more. If you can’t join us in person, watch the livestream of the event virtually! Follow [@PebbleDev](http://twitter.com/PebbleDev) on Twitter or watch the [Meetup page](http://www.meetup.com/PebbleSF/events/221717043/) for the link when it goes live. If all the exciting features and possibilities mentioned above weren’t enough to inspire you to write timeline apps for the rest of your life, today we are also teasing our **Timeline Challenge**. The Timeline Challenge will be a competition to see who can create the most innovative, exciting Pebble applications that use the timeline: winners will receive Pebble Times, awesome swag and exclusive opportunities. Look out for more details coming next Tuesday, April 14th! ## Need more Pebble in your life? Check out our [guides](https://developer.repebble.com/guides.md) and [documentation](https://developer.repebble.com/docs.md) online, fire up [CloudPebble](https://cloudpebble.repebble.com/), and have a ball! As always, don't hesitate to [get in touch](https://developer.repebble.com/contact.md). Love, Team Pebble * * * --- # The Road to Pebble SDK 3.0 in Ten Questions Source: https://developer.repebble.com/blog/2015/04/03/The-Road-To-Pebble-SDK-3.0-In-Ten-Questions/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 April 2015 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/04/03/The-Road-To-Pebble-SDK-3.0-In-Ten-Questions.md#comments) ![](https://developer.repebble.com/assets/images/blog/road30-banner.png) We launched the first version of our new SDK with support for Pebble Time two days into our Kickstarter campaign. Since then, we have updated it every week, releasing six iterations of our developer preview. Some of these versions included major new features like support for color in the emulator and support for the timeline. Some other features were more subtle, like the new antialiased drawing mode and the updates to the animation system. All these changes together, and a bunch more that you have not seen yet, will form the SDK that you will use to build watchfaces, watchapps and timeline apps for all models of Pebble watches in the coming months. These updates include lots of changes, and no matter how hard we try to document everything there are a lot of unanswered questions. Some subjects have just not been addressed by the developer previews yet, some need more explaining and as always there are things we just missed in our communication efforts. In this update, I want to answer publicly the most frequent questions received from the community in the last few weeks. I will cover components that have been released but also those that are still to come so you have the information needed to plan ahead and prepare your apps for the Pebble Time launch. ## What is the “timeline” shown in the Kickstarter video and how can I plug into it? The timeline is a system-provided app that allows the user to navigate through important events in their near future and their past. ![A glipse at the future (on the timeline)](https://developer.repebble.com/assets/images/blog/road30-timeline.gif) Apps have a very important role to play in the timeline. All Pebble apps (but not companion apps that use our native PebbleSDK on Android and iOS) can push “pins” to the timeline. From a developer perspective, pins are just a block of JSON which describes the information to show to the user and what layout to use to display the information (generic, calendar, sports or weather). Pins can also include notifications that will be shown to the user when the pin is created or updated on the watch, and reminders that can trigger at a specific time. Finally pins can have actions attached to them. Actions can open a Pebble app with a `uint32` parameter. You can experiment with pins in CloudPebble’s new “Timeline” tab or with the command line SDK and the `pebble insert-pin` command. For documentation, refer to our [“Understanding Timeline Pins”](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md) guide. Once you have nailed down what you want your pins to look and feel like you can start pushing them via a public HTTP API. This API requires that your app is submitted on the Pebble appstore; that is also how you get your API key to use the timeline API. We have built a [Node.js library](https://github.com/pebble/pebble-api-node) to make this easier and David Moreau has already shared a [PHP library](https://github.com/dav-m85/pebble-api-php/). We also have a number of examples to help you get started: - A very simple [timeline hello world](https://github.com/pebble-examples/hello-timeline) that shows how to push a pin every time you press a button in your app - A [TV show tracker](https://github.com/pebble-examples/timeline-tv-tracker/) that announces your favorite tv show and lets users click on the pins to increase the number of viewers in realtime. Get started now with our [Getting started with Timeline post](https://developer.repebble.com/blog/2015/03/20/Getting-Started-With-Timeline.md) or join us at the next [Pebble SF developer meetup](http://www.meetup.com/PebbleSF/) on April 24th where we will do a walkthrough of how to build a timeline app. If you are unable to make it, you can also find this session on our [YouTube channel](https://www.youtube.com/channel/UCFnawAsyEiux7oPWvGPJCJQ). As a side note, many people have asked us what will happen if their app does not integrate with the timeline. The new Pebble system still includes a main menu in which all the apps will appear and they can be still be launched from there. ## How can I build apps with transitions and animations like the ones shown in the Kickstarter videos? The user interface in the new OS is full of extremely rich animations, and of course we are not limiting them to the system applications. We want all apps to take advantage of them and to achieve that, we are investing a lot of effort. An essential part of all the animations is the core graphics framework used to draw shapes, text and images. We have improved it a lot in recent weeks with [much better support for animations](https://developer.repebble.com/guides/graphics-and-animations/animations.md), [anti-aliasing and stroke width](https://developer.repebble.com/guides/graphics-and-animations/drawing-primitives-images-and-text.md), color bitmap handling and a lot of bugfixes and performance improvements. Upcoming releases of the SDK will add the new UI components that you saw in the Kickstarter video: - A new MenuLayer with support for color, infinite scrolling (i.e. looping back to the top), and new animations ![The new MenuLayer >{pebble-screenshot,pebble-screenshot--time-red}](https://developer.repebble.com/assets/images/blog/road30-menulayer.gif) - A new ActionBar (available this week in developer preview 6!) that is wider, takes the full height of the screen, and includes some new animations. ![The updated ActionBar >{pebble-screenshot,pebble-screenshot--time-red}](https://developer.repebble.com/assets/images/blog/road30-actionbar.png) - An ActionMenuWindow which is a full screen component to select an action in a list. This is the same UI used to select an action on a pin or a notification, and it is available to all apps. ![The new ActionMenuWindow >{pebble-screenshot,pebble-screenshot--time-red}](https://developer.repebble.com/assets/images/blog/road30-actionmenu.gif) - A StatusBarLayer that gives you much more control over the status bar, including the ability to animate it and add goodies like a progress indicator and a card counter for the card pattern. ![The new StatusBar >{pebble-screenshot,pebble-screenshot--time-red}](https://developer.repebble.com/assets/images/blog/road30-statusbar.png) - Cards are not really a UI Component but more of a standard pattern, seen in the Weather app in the Kickstarter video and we expect that a lot more apps will want to re-use it because it is an extremely efficient way to represent information and looks very good! We will have a full example available to you very soon showing how to build this type of app. ![A preview of the Weather app and its animations >{pebble-screenshot,pebble-screenshot--time-red}](https://developer.repebble.com/assets/images/blog/road30-card.gif) For more information on the design of apps in SDK 3.0, we are putting the finishing touches on a completely new design guide. Stay tuned! ## What is up with the icons on Pebble Time? Can I use these effects in my applications? A lot of the new design aesthetic is provided by icons, and those icons are not your usual bitmap file. They animate during transitions to give a level of life and personality never seen in any User Interface framework before. They are vector based animations and we came up with a complete new set of APIs called Pebble Drawing Commands, as well as a new file format (.pdc) to implement them. Compared to bitmap icons they can be automatically animated by the system depending on the context: the same icon can explode on the screen, regroup into a dot or fly through one of the screen edges; all of this in a fraction of the space it would normally take to do such effects with animated PNGs. We will be providing a large set of icons that you can use in your timeline pins and in your apps. You will also be able to make your own animated icons to play in your app. ## What is really happening when I compile an app today to be compatible with all Pebbles? With the current version of the Pebble SDK, we compile your apps once with SDK 2.9 for Aplite (Pebble and Pebble Steel) and once with SDK 3.0 for Basalt (Pebble Time and Pebble Time Steel). This means that your apps can have a different look on each platform. This is going to be even more true as we introduce the new UI components in the next few releases. ![Aplite differences](https://developer.repebble.com/assets/images/blog/road30-aplite-differences.png) However, as soon as we provide official support for Aplite in SDK 3.0, we will start compiling against the same set of APIs and the UI components will have similar sizes, look and behavior. All the new software features of Pebble Time will be available on the original Pebble, including the timeline, as well as the new UI components, PNG, Pebble Drawing Commands, unlimited apps, AppFaces, etc. Of course features that are hardware-dependent (such as the microphone) will not be supported on Aplite. We encourage everyone to maintain support for both Aplite and Basalt in their source code. You can easily do it with `#ifdef` calls. If you just want to freeze your Aplite app as it is today and focus on your Basalt app this will be possible too with a new feature in the build process coming in the near future. ## What are AppFaces and when can we use them? AppFaces are previews of your app that are displayed in the launcher before the user starts your app. To use them, your app will need to support being launched without a UI to update the AppFace. ![AppFaces](https://developer.repebble.com/assets/images/blog/road30-appfaces.png) AppFaces will not be part of Pebble SDK when we launch Pebble Time. Your apps will just appear with their name until we add this API at a later stage. This also means that your app icons defined in the appinfo.json are not used with the new launcher (but they will still appear on Aplite until 3.0 ships for Aplite). ## When can we start interacting with smartstraps? We have released a lot of [mechanical and electrical information about smartstraps](https://developer.repebble.com/smartstraps/) but no APIs yet. You will quickly find out that without software support, the smartstrap does not get any power. We are working hard with our first smartstrap partners to finalize the specifications for this API. If you want to be a part of this discussion, [please make yourself known!](https://developer.repebble.com/contact.md) ## How can I get my hands on Pebble Time? When will users get them? If you did not order one on Kickstarter you can register on [our website](https://rePebble.com/pebble_time/) to be notified as soon as they are available for normal orders. We will ship every Kickstarter pledge before we start selling them on the Pebble website. If you ordered one through the Kickstarter campaign, we are working hard to get it to you as soon as possible. As you know if you are a Kickstarter backer, Pebble Time starts shipping in May. As a developer, you already have access to the emulator. It includes everything you need to work on your app. If you are missing anything, [contact us](https://developer.repebble.com/contact.md). With only about a month left, there is no time to lose! ## What about PebbleKit for iOS and Android? Any improvements? We decided to focus all our efforts on the new UI framework and the timeline. There are no functional changes to PebbleKit in SDK 3.0. However, all Android apps must be recompiled with the new PebbleKit Android library to be compatible with Pebble Time. If you do not recompile, your app will not work with Pebble Time. We have lots of plans for PebbleKit in the future and hope to share them with you soon. If you have suggestions for APIs you’d like to see, please [contact us](https://developer.repebble.com/contact.md) ## Will developers have access to the microphone? What can we do with it? Absolutely. We will give developers access to our speech-to-text APIs. You will be able to show a UI to start a recording and your app will receive the text spoken by the user. This API is coming soon™ - not in 3.0. We are considering other APIs such as direct microphone access. Stay tuned for more information on those. ## Can we submit apps for Pebble Time to the Pebble appstore now? No, you should not. As long as you are using a developer preview SDK, the APIs are non-final and apps built with the developer preview SDK may not work with the final firmware and SDK shipped with Pebble Time. Once the APIs are stable, we will announce a final SDK and encourage everyone to push applications built with the final SDK to the Pebble appstore. ## Want more answers? For more questions and answers, such as the future of the `InverterLayer`, information on floating point support or whether Pebble is really powered by unicorns and rainbows, please take a look at this [/r/pebbledevelopers thread](http://www.reddit.com/r/pebbledevelopers/comments/314uvg/what_would_you_like_to_know_about_pebble_sdk_30/). Much thanks to all the developers who contributed questions to prepare this blog post! If there is anything else you would like to know, [this thread](http://www.reddit.com/r/pebbledevelopers/comments/314uvg/what_would_you_like_to_know_about_pebble_sdk_30/) is just [one](https://forum.repebble.com) [of the](https://twitter.com/pebbledev) [many ways](https://developer.repebble.com/contact.md) [to get in touch](http://www.meetup.com/pro/pebble/) with us and get answers! * * * --- # Pebble at Pearl Hacks Source: https://developer.repebble.com/blog/2015/03/27/Pebble-at-PearlHacks/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 27 March 2015 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2015/03/27/Pebble-at-PearlHacks.md#comments) ![](https://developer.repebble.com/assets/images/blog/032715-girls.jpg) Last weekend, we were so excited to watch a new group of Pebble Developers blossom. Pearl Hacks, an all women’s hackathon hosted at the University of North Carolina at Chapel Hill, served as an incubator for a class of young women who are a testament to the diversity of backgrounds represented by the Pebble Developer community. Were these women seasoned hackers? Nope, 60% of them had never been to a hackathon before, and the majority of the attendees had been programming for less than a year! Regardless of their previous technical experience, they created spectacular projects, and we were so impressed by their practical, useful hacks. When we started our workshop to help first time developers get started with Pebble, we had no idea just how many projects would be created over the course of the weekend! Only 4 people in the room of about 40 people had ever heard of Pebble before but in just 24 hours we had 15 teams create Pebble projects! [![](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-01_thumb.jpg)](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-01.jpg)[![](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-02_thumb.jpg)](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-02.jpg) The most outstanding Pebble hack was awarded to [Pebble Pane](http://pearlhacks.challengepost.com/submissions/34726-pebble-pane) a Pebble app that allows you to customize your computer screen for different activities. For example, if you want to be in “coding mode,” pressing the select button on your Pebble signals your computer to run an AppleScript that in turn opens applications (such as Sublime and Spotify) and fits them to predefined window size and locations. We all agreed that this was an app we would personally use, and were so impressed by the group’s ability to overcome multiple challenges during the hacking process. Our second best Pebble hack is sure to excite our League of Legends fans: [League Streams Watchapp](http://pearlhacks.challengepost.com/submissions/34687-league-streams-watchapp) allows Pebblers to see the top 5 League streamers on Twitch in their vicinity. League Streams was ready for publishing by the Demo Fair, and we’re sure that it will be avidly used as soon as it hits the Pebble appstore. From the high school students who used our SDK to learn C and wrote an app in under 24 hours, to the Chemical Engineering PhD student who learned JavaScript to integrate her website with her Pebble app, this weekend truly proved that anyone can be an exceptional Pebble Developer. No matter your prior experience, we encourage you to check out our [guides](https://developer.repebble.com/guides.md) and [documentation](https://developer.repebble.com/docs.md) online. Don’t be shy about [asking for help](https://developer.repebble.com/contact.md): we can’t wait to see what you create! [![](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-03_thumb.jpg)](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-03.jpg)[![](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-04_thumb.jpg)](https://developer.repebble.com/assets/images/blog/032715-pearlhacks-04.jpg) Pebblers in Los Angeles or the College Park, Maryland area - we’re coming to you very soon! We’ll be at LA Hacks from April 3rd-5th and Bitcamp from April 10th-12th, and we’d love to see you. Stop by our table and come hang out! Check out our [events page](https://developer.repebble.com/community/events.md) for more information about where we’ll be in the upcoming months. **Have you created an app at a hackathon but haven't submitted it to the appstore yet? We would love for you to show your app to the world! Head over to the [Developer Dashboard](https://dev-portal.getpebble.com/developer) to get started.** Much love from our team and we'll see you at the [next event](https://developer.repebble.com/community/events.md)! * * * --- # Getting Started With Timeline Source: https://developer.repebble.com/blog/2015/03/20/Getting-Started-With-Timeline/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 20 March 2015 - [Kirby Kohlmorgen](https://developer.repebble.com/blog/authors/kirby/) - [Timeline](https://developer.repebble.com/blog/tags/timeline/) - [Comments](https://developer.repebble.com/blog/2015/03/20/Getting-Started-With-Timeline.md#comments) ![](https://developer.repebble.com/assets/images/blog/getting-started-timeline.png) The new timeline interface is a completely new way to have users interact with your app. The Pebble SDK along with timeline web APIs allows you to push pins to your users' Pebbles. Adding pins to timeline is a straightforward process. This guide will walk you through the steps from start to finish. ## The Project We're going to build an app that lets users track their packages. We'll place pins on the timeline for each package. For simplicity, we'll use the [Slice API](https://developer.slice.com/) to get information about our packages. We'll go over the specifics of how to: - Use the [PebbleKit JS timeline APIs](https://developer.repebble.com/guides/pebble-timeline/timeline-js.md) - Setup a server that utilizes the [pebble-api](https://www.npmjs.com/package/pebble-api) npm module - Enable timeline for the app through the [Developer Dashboard](https://developer.repebble.com/dashboard) ## Setup Before we dive into the timeline specifics of this app we'll first need to build a typical Pebble app. I won't spend much time on how to build a basic Pebble app; there are plenty of [guides](https://developer.repebble.com/guides.md) for that. In order to get setup with Slice follow their [Hello World example](https://developer.slice.com/docs/hello). After you have your OAuth token it is easy to make a request to their [shipments endpoint](https://developer.slice.com/docs/resources#res_shipments) and get a JSON array of all your shipments. For example: ``` $ curl -X GET --header 'Authorization: XXXXXXXXXXXXXXXXXXXXXXXXXX' \https://api.slice.com/api/v1/shipments# response{"result": [{"status": {"code": 3,"description": "DELIVERED"},"updateTime": 1426785379000,"shipper": {...},"description": "Dockers Pants, Tapered Fit Alpha Khaki Flat Front Dark Pebble 34x32","receivedDate": "2015-03-20","receivedShare": null,"trackingUrl": "https://tools.usps.com/go/TrackConfirmAction.action?tLabels=9261299998829554536102","shares": [...],"merchantTrackingUrl": null,"emails": [...],"href": "https://api.slice.com/api/v1/shipments/2689846985907082329","shippingDate": "2015-03-17","items": [...],"shippingEstimate": {...},"trackingNumber": "9261299998829554536102","deliveryEstimate": {"maxDate": "2015-03-20","minDate": "2015-03-20"},"destinationAddress": {...},"history": [...]}, ...]} ``` Our app will be pretty simple. We'll use [`AppSync`](https://developer.repebble.com/docs/c/Foundation/AppSync.md "AppSync") to load a [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") with a list of packages that we get from Slice. ![package tracker app](https://developer.repebble.com/assets/images/blog/package-tracker.png) Here is the source code to [timeline-package-tracker](https://github.com/pebble-examples/timeline-package-tracker/tree/7edde5caa0f6439d2a0ae9d30be183ace630a147) without any timeline support. > Note that this is just an example. In a real app, you'd never hard code an OAuth token into javascript, and you probably wouldn't arbitrarily limit your app to have a 3 package limit. ## PebbleKit JS Timeline APIs For our app we simply want to push each package as a pin to the timeline. Before we can do this we need to get our user's timeline token. After we get the token we'll need to send it to our own server. We'll also send our user's Slice oauth token so that the server can keep up to date on all of their packages. From there we can format a timeline pin with our package information and send it to the user via their timeline token. ``` var API\_ROOT = 'YOUR\_TIMELINE\_SERVER';var oauth= 'XXXXXXXXXXXXXXXXXXXX'; // in a real app we'd use a configuration windowPebble.addEventListener('ready', function() {doTimeline();});var doTimeline = function(packages) {Pebble.getTimelineToken(function (token) {sendToken(token, oauth);}, function (error) {console.log('Error getting timeline token: ' + error);});};var sendToken = function(token, oauth) {var request = new XMLHttpRequest();request.open('GET', API\_ROOT + '/senduserpin/' + token + '/' + oauth, true); // send the user's timeline token and Slice oauth token to our serverrequest.onload = function() {console.log('senduserpin server response: ' + request.responseText);};request.send();} ``` > Note that we're currently talking to a server that doesn't exist yet! We'll cover how to set that up next. ## Pebble Timeline Web APIs We'll need a server of our own to talk to the Pebble timeline web APIs. [Express](https://github.com/strongloop/express/) is a convenient and easy way to get a server up and running quickly. It also allows us to utilize the [pebble-api](https://www.npmjs.com/package/pebble-api) npm module. A basic Express server look like this: ``` var express = require('express');var request = require('request');var Timeline = require('pebble-api');var app = express();app.set('port', (process.env.PORT || 5000));var timeline = new Timeline();app.get('/', function(req, res) {res.send('Hello, world!');});// start the webservervar server = app.listen(app.get('port'), function () {console.log('Package Tracker server listening on port %s', app.get('port'));}); ``` We'll go ahead and include `request`, so that we can easily make requests to Slice to get each user's package information. We'll also include the `pebble-api` module too. Now that we have a basic server up all we need to do is handle each request to our server, fetch packages, and send pins. The `pebble-api` module will make it super easy to create our pins according to [spec](https://developer.repebble.com/guides/pebble-timeline/pin-structure.md). ``` var users = {}; // This is a cheap "in-memory" database ;) Use mongo db in real life!app.get('/senduserpin/:userToken/:oauth', function(req, res) {var userToken = req.params.userToken;var oauth= req.params.oauth;users[userToken] = {};users[userToken]['oauth'] = oauth; // store userusers[userToken]['packages'] = [];res.send('Success');});// every 5 minutes check for new packages for all userssetInterval(function() {Object.keys(users).forEach(function(user) {var userToken = user;var oauth= users[user]['oauth'];getPackages(oauth, userToken);});}, 300000);var getPackages = function(oauth, userToken) {var options = {url: 'https://api.slice.com/api/v1/shipments',headers: { 'Authorization': oauth }};request(options, function(error, response, body) {var response = JSON.parse(body);var pkgs = getCurrentPackages(response.result);pkgs.forEach(function(pkg) {var found = false;users[userToken]['packages'].forEach(function(oldPkg) { // check if pkg is new or notif(oldPkg.id === pkg.id) {found = true;}});if(!found) {users[userToken]['packages'].push(pkg) // we have a new package, save itsendPin(pkg, userToken); // and send it as a pin}});});};// slice returns every package we've ever ordered. Let's just get the ones from the past week.var getCurrentPackages = function(pkgs) {current = [];var oneWeekAgo = (new Date).getTime() - 604800000;pkgs.forEach(function(pkg) {if(pkg.updateTime \> oneWeekAgo) {current.push({'name': pkg.description, 'date': pkg.shippingEstimate.minDate, 'id': pkg.trackingNumber});}});return current.slice(0, 3);};var sendPin = function(pkg, userToken) {var pin = new Timeline.Pin({id: pkg.id,time: new Date(Date.parse(pkg.date) + 43200000), // will always be noon the day of deliverylayout: new Timeline.Pin.Layout({type: Timeline.Pin.LayoutType.GENERIC\_PIN,tinyIcon: Timeline.Pin.Icon.MAIL,title: pkg.name})});timeline.sendUserPin(userToken, pin, function (err, body, resp) {if(err) {return console.error(err);}});}; ``` This will accept GET requests from our Pebble app, and will then store each user in memory. From there it checks every 5 minutes for new packages, and if they exist creates a pin for them. Since the carrier only tells us the date the package will be delivered, we arbitrarily set the time of the pin to noon. > Note in this current implementation users are stored in memory. A better implementation would store packages in a database. ## Enable Timeline in the Developer Dashboard We have almost everything setup, but our app won't work correctly with the timeline web APIs until we upload the app's pbw to the Pebble Developer Dashboard and enable timeline. In order to enable timeline, perform the following steps: 1. Make sure you have a new and unique uuid. If you used the example from GitHub you will have to change it in your `appinfo.json`. To generate a new UUID, [you can use this tool](https://www.uuidgenerator.net/version4) 2. Login to the [Developer Dashboard](https://developer.repebble.com/dashboard) 3. Click the Add Watchapp button on the portal (you don't need to upload any image assets) 4. Upload your pbw by adding a release (located in the build folder of your project) 5. Click the Enable Timeline button And then you're done! ![package tracker pin](https://developer.repebble.com/assets/images/blog/package-tracker-pin.png) Checkout the full source code on [Github](https://github.com/pebble-examples/timeline-package-tracker/). > **Further Reading on Pebble Timeline** > > For more information, we suggest you take a look at the [timeline guides](https://developer.repebble.com/guides/pebble-timeline.md) as well as the [hello-timeline](https://github.com/pebble-examples/hello-timeline) and the [timeline-tv-tracker](https://github.com/pebble-examples/timeline-tv-tracker) examples. They are all great resources to make the most of timeline! * * * --- # Get Ready For Smartstraps - Warm Up Your 3D Printer! Source: https://developer.repebble.com/blog/2015/03/03/Get-Ready-For-Smartstraps/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 March 2015 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/03/03/Get-Ready-For-Smartstraps.md#comments) Many of you have read about smartstraps on [our Kickstarter page](http://kickstarter.com/projects/597507018/pebble-time-awesome-smartwatch-no-compromises) and contacted us for more details. Today we are releasing mechanical and electrical designs so you can start working on the next generation of straps: Straps that expand the functionality of Pebble Time and Pebble Time Steel. The smartstrap connector is situated on the back of your new favorite watch. Smartstraps can extend below the watch to reach that connector while Pebble Time is worn. In the 3D files we are releasing today, you will find a generic connector that can work with most straps. Of course, you can also design completely new straps if your project calls for one! ![strap-components](https://developer.repebble.com/assets/images/more/strap-components.png) The smartstrap connector has four contacts: two for ground, one for power in/out and a one-wire serial bus. The power contact is bi-directional and can be used to power your accessory, or for the strap to provide power to the watch. The amount of power you can draw to power your accessory must not exceed 20 mA, and of course it will impact the battery life of the Pebble. For more information on how to electrically connect the smartstrap connector to an external accessory, please refer to our [dedicated smartstrap page](https://developer.repebble.com/smartstraps/). The APIs to interact with smartstraps are not part of the developer preview. They will be available publicly several months after we start shipping Pebble Time, as it is lower priority than shipping the main OS. You can however expect that there to be APIs that read and write on the port, as well as some standard control protocol to detect when a smartstrap is attached and even automatically launch an app. We will have an early access program to the smartstraps APIs for developers interested in them. Just [drop your email into this form](http://eepurl.com/bfK91H) and we will contact you when we are ready to share them. Until then you can also exchange ideas and [discuss smartstraps in the forums](https://forums.getpebble.com/categories/smartstraps). * * * --- # Time to Add Color to These Pebbles Source: https://developer.repebble.com/blog/2015/02/26/KS-update-for-developers/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 26 February 2015 - [Team Pebble](https://developer.repebble.com/blog/authors/pebble/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/02/26/KS-update-for-developers.md#comments) ![](https://developer.repebble.com/assets/images/blog/Pebble_Watch_3Up_Small.png) You [might](https://www.kickstarter.com/blog/six-hours-in-pebble-time) [have](http://techcrunch.com/2015/02/24/pebble-smashes-through-6-5m-on-kickstarter-in-half-a-day/) [noticed](http://www.cnn.com/videos/tech/2015/02/25/ns-pebble-time-smartwatch-ceo.cnn) [on](http://www.cnbc.com/id/102455422) [the](http://www.theverge.com/2015/2/24/8091175/pebble-time-watch-wearable-platform-eric-migicovsky-interview) [Internet](http://www.reddit.com/r/IAmA/comments/2x5aj4/were_eric_migicovsky_the_creators_of_pebble_time/) that we announced the brand new Pebble Time on Tuesday. Almost 50,000 members of our community have already backed the [project on Kickstarter](https://www.kickstarter.com/projects/597507018/pebble-time-awesome-smartwatch-no-compromises) this week. Most important for all of you is that today we are announcing the first developer preview of Pebble SDK 3.0. This new SDK is available right now to all Pebble developers, via [CloudPebble](https://cloudpebble.repebble.com/) or to [install on your computer](https://developer.repebble.com/sdk/download/). Both versions include an emulator with support for the new hardware, and of course the color screen! ![](https://developer.repebble.com/assets/images/blog/fill-up.gif) ![](https://developer.repebble.com/assets/images/blog/test7.png) ![](https://developer.repebble.com/assets/images/blog/isotime.gif) ![](https://developer.repebble.com/assets/images/blog/block-world.gif) Even better: all current Pebble apps will still work on the new Pebble Time! Apps built with Pebble SDK 3.0 are compatible with today’s Pebble and will be compatible with Pebble Time. Switching to 3.0 only requires minor changes to your application. Once those changes are done, you can easily add color support for Pebble Time watches. By the way, the resolution has not changed (144x168) and we have increased the memory available to apps on Pebble Time to 64kB which should facilitate the transition. > **For help porting your app to 3.0, take a look at our Migration Guide.** We are also sharing with you the documentation for the timeline. As explained in our [Kickstarter video](https://d2pq0u4uni88oo.cloudfront.net/assets/003/333/414/b0eadb909090b2df2b6a5b7a54a900d1_h264_high.mp4), the timeline is a key component of Pebble Time, and it will also be supported by Pebble and Pebble Steel later this year. The timeline is built around a few new PebbleKit JavaScript functions and web APIs that you will call from your own backend. Please note that the API endpoints are not available yet. > **Read the new [Pebble timeline guide](https://developer.repebble.com/guides/pebble-timeline.md).** There are a lot of other new features in 3.0 such as: - Support for 64 colors which all [have a name](https://developer.repebble.com/tools/color-picker/)! - A brand new [animation framework](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation") - PNG and Animated PNG support in the SDK - Detecting which platforms you are building for at compile time - Much more to come! > **For more details of these and other features, take a look at [What’s New in SDK 3.0](https://developer.repebble.com/sdk/whats-new/).** Much Love and Happy Pebbling! - Team Pebble * * * --- # Bezier Curves and GPaths on Pebble Source: https://developer.repebble.com/blog/2015/02/13/Bezier-Curves-And-GPaths/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 13 February 2015 - [Łukasz Zalewski](https://developer.repebble.com/blog/authors/lukasz/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2015/02/13/Bezier-Curves-And-GPaths.md#comments) ![](https://developer.repebble.com/assets/images/blog/bezier-banner.png) Drawing complex paths requires a lot of manual work on Pebble. Here I'll show you how to do this efficiently and quickly using a Pebble-optimized [`GPath`](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md#GPath "GPath") algorithm and Bezier curves. ## Why Bezier Curves? Typically, when a developer wants to create curve-based graphics on Pebble he has to use prepared bitmaps or complex GPaths (or generate paths with [svg2gpath](http://gpathsvg.org/)). Both of those approaches have serious downsides: bitmaps require resource space to store, memory to process and rotating bitmaps is a complex operation. Detailed GPaths, while fast in processing, can take a long time to design without special tools. Bezier curves are a very interesting solution, which may seem computationally excessive but solves the obstacles of both the previous methods. Because they're so simple to use they are common elements in many graphic systems, primarly vector based graphics. ## Drawing Bezier Curves The mathematical formula for a Bezier curve can be found on [Wikipedia](http://en.wikipedia.org/wiki/Composite_B%C3%A9zier_curve). We can fairly easily turn it into C code, but we have to make a couple of important decisions. The formula itself is linear and requires us to decide how many steps we want to take in order to draw our Bezier curve. It's safe to assume that calculating 1000 points on our Bezier curve should be enough to display a continuous line on the small Pebbles screen. ``` void naive\_bezier(GContext \*ctx, GPoint points[]) {for (double t = 0.0; t\<1.0; t += 0.001) {double tx = pow\_d(1-t, 3) \* points[0].x + 3 \* t \* pow\_d(1-t, 2) \* points[1].x +3 \* pow\_d(t, 2) \* (1-t) \* points[3].x + pow\_d (t, 3) \* points[2].x;double ty = pow\_d(1-t, 3) \* points[0].y + 3 \* t \* pow\_d(1-t, 2) \* points[1].y +3 \* pow\_d(t, 2) \* (1-t) \* points[3].y + pow\_d(t, 3) \* points[2].y;graphics\_draw\_pixel(ctx, GPoint(tx, ty));}} ``` **Notes:** - `pow_d(double a, int b)` is simply a function which returns `b` to the power of `a`. - `points` contains array of 4 points used as parameters for drawing the Bezier curve. While this approach gives us precise results its also very computationally expensive. Most of the work done by the CPU is redundant as most of those points overlap each other due to the low screen resolution. ## Optimizing for Pebble The simplest optimization would be reducing the number of steps and drawing lines between computed points. While this will significantly reduce calculation time, it will also make the curve less precise and pleasing to the eye. But what if we could dynamically calculate the number of segments needed to create perfectly smooth curve? Here's a method published by Maxim Shermanev which you can find out more about on his website: [www.antigrain.com](http://www.antigrain.com/research/adaptive_bezier/index.html). Below you can see code from his research adapted to work efficiently on the Pebble architecture: ``` void recursive\_bezier\_fixed(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4){// Calculate all the mid-points of the line segmentsint x12= (x1 + x2) / 2;int y12= (y1 + y2) / 2;int x23= (x2 + x3) / 2;int y23= (y2 + y3) / 2;int x34= (x3 + x4) / 2;int y34= (y3 + y4) / 2;int x123= (x12 + x23) / 2;int y123= (y12 + y23) / 2;int x234= (x23 + x34) / 2;int y234= (y23 + y34) / 2;int x1234 = (x123 + x234) / 2;int y1234 = (y123 + y234) / 2;// Angle Conditionint32\_t a23 = atan2\_lookup((y3 - y2) / fixedpoint\_base, (x3 - x2) / fixedpoint\_base);int32\_t da1 = abs(a23 - atan2\_lookup((y2 - y1) / fixedpoint\_base, (x2 - x1) / fixedpoint\_base));int32\_t da2 = abs(atan2\_lookup((y4 - y3) / fixedpoint\_base, (x4 - x3) / fixedpoint\_base) - a23);if(da1 \>= TRIG\_MAX\_ANGLE) da1 = TRIG\_MAX\_ANGLE - da1;if(da2 \>= TRIG\_MAX\_ANGLE) da2 = TRIG\_MAX\_ANGLE - da2;if(da1 + da2 \< m\_angle\_tolerance){// Finally we can stop the recursionadd\_point(x1234 / fixedpoint\_base, y1234 / fixedpoint\_base);return;}// Continue subdivisionrecursive\_bezier\_fixed(x1, y1, x12, y12, x123, y123, x1234, y1234); recursive\_bezier\_fixed(x1234, y1234, x234, y234, x34, y34, x4, y4); }bool bezier\_fixed(GPathBuilder \*builder, GPoint p1, GPoint p2, GPoint p3, GPoint p4) {// Translate points to fixedpoint realmsint32\_t x1 = p1.x \* fixedpoint\_base;int32\_t x2 = p2.x \* fixedpoint\_base;int32\_t x3 = p3.x \* fixedpoint\_base;int32\_t x4 = p4.x \* fixedpoint\_base;int32\_t y1 = p1.y \* fixedpoint\_base;int32\_t y2 = p2.y \* fixedpoint\_base;int32\_t y3 = p3.y \* fixedpoint\_base;int32\_t y4 = p4.y \* fixedpoint\_base;if (recursive\_bezier\_fixed(builder, x1, y1, x2, y2, x3, y3, x4, y4)) {return gpath\_builder\_line\_to\_point(builder, p4);}return false;} ``` **Notes:** - This code uses fixedpoint integers since the Pebble CPU doesn't support floating point operations. You can find more about that in a talk given by Matthew Hungerford during 2014 Pebble Developer Retreat (video available [here](https://www.youtube.com/watch?v=8tOhdUXcSkw)). - To determine the angle, the algorithm calculates parameters of the curve at given points. This implementation is very effective since Pebble's `atan2_lookup` is just looking up that value in a precomputed table. - `m_angle_tolerance` is the angle of the curve we're looking for, expressed in degrees. In our case it's 10 degrees: `int32_t m_angle_tolerance = (TRIG_MAX_ANGLE / 360) * 10;` ## Applying Code to GPath In order to make it easy for developers, we have prepared the GPathBuilder library which will ease the process of creating GPaths out of a few Bezier curves and/or lines. The resulting path can already be manipulated with the [existing APIs](https://developer.repebble.com/docs/c/Graphics/Drawing_Paths.md). You can find it on the [pebble-hacks Github page](https://github.com/pebble-hacks/gpath-bezier) along with a simple demo app. Usage is extremely simple. Here are the main functions in the API: - `GPathBuilder* gpath_builder_create(uint32_t max_points)` will create the GPathBuilder object you will need to use in order to create a GPath with Bezier curves. `max_points` sets the limit on number of points created in the process. - `void gpath_builder_destroy(GPathBuilder *builder)` will destroy the GPathBuilder object and free the memory it used. - `bool gpath_builder_line_to_point(GPathBuilder *builder, GPoint to_point)` will create a straight line from last point to the given point. - `bool gpath_builder_curve_to_point(GPathBuilder *builder, GPoint to_point, GPoint control_point_1, GPoint control_point_2)` will create a Bezier curve from the last point to a given point (`to_point`), `control_point_1` and `control_point_2` are used as parameters for the Bezier curve for last point and given point respectively. - `GPath* gpath_builder_create_path(GPathBuilder *builder)` will return a GPath object ready to be used in your graphic routine. Remember that the GPathBuilder is not being destroyed in the process and you have to do that manually. Below is shown a simple shape involving two curves and the code required to create the path: ![result](https://developer.repebble.com/assets/images/blog/bezier-result.png) ``` // Create GPathBuilder objectGPathBuilder \*builder = gpath\_builder\_create(MAX\_POINTS);// Move to the starting point of the GPathgpath\_builder\_move\_to\_point(builder, GPoint(0, -60));// Create curvegpath\_builder\_curve\_to\_point(builder, GPoint(60, 0), GPoint(35, -60), GPoint(60, -35));// Create straight linegpath\_builder\_line\_to\_point(builder, GPoint(-60, 0));// Create another curvegpath\_builder\_curve\_to\_point(builder, GPoint(0, 60), GPoint(-60, 35), GPoint(-35, 60));// Create another straight linegpath\_builder\_line\_to\_point(builder, GPoint(0, -60));// Create GPath object out of our GPathBuilder objects\_path = gpath\_builder\_create\_path(builder);// Destroy GPathBuilder objectgpath\_builder\_destroy(builder);// Get window boundsGRect bounds = layer\_get\_bounds(window\_get\_root\_layer(window));// Move newly created GPath to the center of the screengpath\_move\_to(s\_path, GPoint((int16\_t)(bounds.size.w/2), (int16\_t)(bounds.size.h/2))); ``` And there you have it, complex GPaths built with a few lines of code. ## Conclusion This library should make it easier to create amazing graphics on Pebble and can be used in animations as it's lightweight and fast. Bear in mind that the GPathBuilder temporarily uses RAM proportinal to `max_points` until `gpath_builder_destroy` is called. You can find the example app and library code on the [pebble-hacks Github page](https://github.com/pebble-hacks/gpath-bezier). We are also looking forward to this technology being used in online tools such as [SVG to Pebble GPath Converter](http://gpathsvg.org/) by awesome Pebble developer [Rajendra Serber](https://github.com/ardnejar). * * * --- # Pebble Emulator 2/2 - JavaScript and CloudPebble Source: https://developer.repebble.com/blog/2015/01/30/Pebble-Emulator-JavaScript-Simulation/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 30 January 2015 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Down the Rabbit Hole](https://developer.repebble.com/blog/tags/down-the-rabbit-hole/) - [Comments](https://developer.repebble.com/blog/2015/01/30/Pebble-Emulator-JavaScript-Simulation.md#comments) This is another in a series of technical articles provided by the members of the Pebble software engineering team. This article describes some recent work done at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short for Quick EMUlator, is a generic, open source machine emulator and virtualizer). > This post is part 2 of 2, and details the work undertaken to provide emulation of the PebbleKit JS and CloudPebble aspects of emulating a Pebble in its entirety. Read [_Pebble Emulator 1/2 - QEMU For Pebble_](https://developer.repebble.com/blog/2015/01/30/Development-Of-The-Pebble-Emulator.md) for information on the creation of the Pebble Emulator. For developers of third-party apps it is insufficient to emulate just the Pebble itself, as most non-trivial apps also run JavaScript on the phone to provide the watchapp with additional information. Furthermore, some apps are written entirely in JavaScript using Pebble.js; it is important to support those as well. We therefore decided to implement support for running JavaScript in tandem with the emulated Pebble. The JavaScript simulator is called [_pypkjs_](https://github.com/pebble/pypkjs). ## JavaScript Runtimes Since our JavaScript environment primarily consists of standard HTML5 APIs, we initially tried building on top of [PhantomJS](http://phantomjs.org). However, we quickly ran into issues with the very old version of WebKit it uses and a lack of flexibility in implementing the functionality we needed, so we abandoned this plan. Our second attempt was to use [node.js](http://nodejs.org), but this proved impractical because it was difficult to inject additional APIs into modules before loading them, by which time they may have already tried to use them. Furthermore, some libraries detected that they were running in node and behaved differently than they would in the mobile apps; this discrepancy proved tricky to eliminate. We ultimately chose to build directly on top of [Google’s V8 JavaScript Engine](https://code.google.com/p/v8/), making use of the [PyV8](https://code.google.com/p/pyv8/) Python bindings to reduce the effort involved in writing our APIs. This gave us the flexibility to provide exactly the set of APIs we wanted, without worrying about the namespace already being polluted. PyV8 made it easy to define these without worrying about the arcana of the C++ V8 interface. ## Threading and Event Handling JavaScript is intrinsically single-threaded, and makes heavy use of events. In order to provide event handling, we used [gevent](http://www.gevent.org) to provide support the key support for our event loop using greenlets. The “main thread” of the interpreter is then a single greenlet, which first evaluates the JavaScript file and then enters a blocking event loop. The PebbleKit JS program is terminated when this event loop ends. Any calls to asynchronous APIs will then spawn a new greenlet, which will ultimately add a callback to the event queue to take effect on the main thread. PebbleKit JS provides for single and repeating timers, which can take either a function to call or a string to be evaluated when the timer expires. We implemented these timers by spawning a new greenlet that sleeps for the given period of time, then places either the function call or a call to eval on the event queue. ## HTML5 APIs Since we are using V8 without any additions (e.g. from Chromium), none of the standard HTML5 APIs are present. This works in our favour: we only support a restricted subset for PebbleKit JS. Our Android and iOS apps differ in their support, so we chose to make the emulator support only the common subset of officially supported APIs: XMLHttpRequest, localStorage, geolocation, timers, logging and performance measurement. We implemented each of these in Python, exposing them to the app via PyV8. We additionally support standard JavaScript language features; these are provided for us by V8. ## LocalStorage LocalStorage provides persistent storage to PebbleKit JS apps. It is a tricky API to implement, as it has properties that are unlike most objects in JavaScript. In particular, it can be accessed using either a series of method calls (`getItem`, `setItem`, `removeItem`, etc.), as well as via direct property access. In either case, the values set should be immediately converted to strings and persisted. Furthermore, iterating over the object should iterate over exactly those keys set on it, without any additional properties. Correctly capturing all of these properties took three attempts. Our first approach was to directly provide a Python object to PyV8, which implemented the python magic methods ` __setattr__ `, ` __delattr__ ` and ` __getattr__ ` to catch attribute access and handle them appropriately. However, this resulted in ugly code and made it impossible to correctly implement the LocalStorage iteration behaviour as PyV8 does not translate non-standard python iterators. The second approach was to create a native JavaScript object using Object.create, set the functions on it such that they would not appear in iteration, and use an ECMAScript 6 (“ES6”) Observer to watch for changes to the object that should be handled. This approach failed on two fronts. The primary issue was that we could not catch use of the delete operator on keys set using property accessors, which would result in values not being removed. Secondly, Observers are asynchronous. This made it impossible to implement the immediate cast-to-string that LocalStorage performs. The final approach was to use an ES6 Proxy to intercept all calls to the object. This enabled us to synchronously catc property accesses to cast and store them. It also provided the ability to provide custom iteration behaviour. This approach lead to a clean, workable and fully-compliant implementation. ## Timers PebbleKit JS provides for single and repeating timers, which can take either a function to call or a string to be evaluated when the timer expires. We implemented these timers by spawning a new greenlet that sleeps for the given period of time, then places either the function call or a call to eval on the event queue. ## Geolocation PebbleKit JS provides access to the phone’s geolocation facilities. According to the documentation, applications must specify that they will use geolocation by giving the ‘location’ capability in their manifest file. In practice, the mobile apps have never enforced this restriction, and implementing this check turned out to break many apps. As such, geolocation is always permitted in the emulator, too. Since there is no geolocation capability readily available to the emulator, it instead uses the MaxMind GeoIP database to look up the user’s approximate location. In practice, this works reasonably well as long as the emulator is actually running on the user’s computer. However, when the emulator is _not_ running on the user’s computer (e.g. when using CloudPebble), the result isn’t very useful. ## XMLHttpRequest Support for XMLHttpRequest is primarily implemented using the Python [_requests_ library](http://docs.python-requests.org/en/latest/). Since requests only supports synchronous requests, and XMLHttpRequest is primarily asynchronous, we spawn a new greenlet to process the send request and fire the required callbacks. In synchronous mode we join that greenlet before returning. In synchronous mode we must also place the _creation_ of the events on the event queue, as creating events requires interacting with V8, which may cause errors while the main greenlet is blocked. ## Pebble APIs PebbleKit JS provides an additional Pebble object for communicating with the watch and handling Pebble accounts. Unlike the HTML5 APIs, these calls have no specification, instead having two conflicting implementations and often vague or inaccurate documentation that ignores the behaviour of edge cases. Testing real apps with these, especially those that do not strictly conform to the documentation, has required repeated revisions to the emulated implementation to match what the real mobile apps do. For instance, it is not clear what should be done when an app tries to send the float _NaN_ as an integer. ## Watch Communication The PebbleKit JS runtime creates a connection to a TCP socket exposed by QEMU and connected to the qemu\_serial device. Messages from PebbleKit JS are exclusively Pebble Protocol messages sent to the bluetooth channel exposed over the Pebble QEMU Protocol. A greenlet is spawned to read from this channel. The primary means of communication available to apps over this channel is AppMessage, a mechanism for communicating dictionaries of key-value pairs to the watch. These are constructed from the provided JavaScript object. It is possible for applications to use string keys; these are replaced with integer keys from the app’s manifest file before sending. If no such key can be found an exception is thrown. This is the documented behaviour, but diverges from the implemented behaviour; both mobile apps will silently discard the erroneous key. When messages are received, a new JavaScript object is created and the messages parsed into JavaScript objects. Here we perform the reverse mapping, converting received integers to string keys, if any matching keys are specified in the app’s manifest. An event is then dispatched to the event loop on the main greenlet. A method is also provided for showing a “simple notification”; again, it is not clear what sort of notification this should be. This implementation sends an SMS notification, which appears to be consistent with what the iOS app does. ## Configuration Pages PebbleKit JS provides the option for developers to show a “configuration page” in response to the user pressing a Settings button in the Pebble mobile app. These configuration pages open a webview containing a user-specified webpage. The mobile apps capture navigation to the special URL ‘pebblejs://close’, at which point they dismiss the webview and pass the URL fragment to the PebbleKit JS app. However, our emulator does not have the ability to present a webview and handle the custom URL scheme, so another approach is required. We therefore pass a new query parameter, ‘return\_to’, to which we pass a URL that should be used in place of the custom URL scheme. Configuration pages therefore must be modified slightly: instead of using the fixed URL, they should use the value of the ‘return\_to’ parameter, defaulting to the old pebblejs://close URL if it is absent. When a URL is opened, the PebbleKit JS simulator starts a temporary webserver and gives a URL for it in the ‘return\_to’ parameter. When that page is loaded, it terminates the webserver and passes the result to the PebbleKit JS app. ## Exception Handling There are three potential sources of exceptions: errors in the user’s JavaScript, error conditions for which we generate exceptions (e.g. invalid AppMessage keys), and unintentional exceptions thrown inside our JavaScript code. In all cases, we want to provide the user with useful messages and JavaScript stack traces. PyV8 supports exceptions, and will translate exceptions between Python and JavaScript: most exceptions from JavaScript will become JSErrors in Python, and exceptions from Python will generally become Exceptions in JavaScript. JSErrors have a stack trace attached, which can be used to report to the user. PyV8 also has some explicit support for IndexErrors (RangeErrors in JavaScript), ReferenceErrors, SyntaxErrors and TypeErrors. When such an exception passes the Python/JavaScript boundary, it is converted to its matching type. This exception conversion support causes a complication: when a support exception crosses from JavaScript to Python, it is turned into a standard Python exception rather than a JSError, and so has no stack trace or other JavaScript information attached. Since many exceptions become one of those (even when thrown from inside JavaScript), and all exception handling occurs in Python, many exceptions came through without any useful stack trace. To resolve this issue, we [forked PyV8](https://github.com/pebble/pyv8) and changed its exception handling. We now define new exceptions for each of the four supported classes that multiple- inherit from their respective Python exceptions and JSError, and still have JavaScript stack information attached. We can then catch these exceptions and display exception information as appropriate. Due to the asynchronous nature of JavaScript, and the heavily greenlet-based implementation of pypkjs, we must ensure that every point at which we call into developer-provided JavaScript does something useful with any exceptions that may be thrown so that JavaScript traces can be passed back to the developer. Fortunately, the number of entry points is relatively small: the event system and the initial evaluation are the key points to handle exceptions. ## Sandboxing While PyV8 makes it very easy to just pass Python objects into JavaScript programs and have them treated like standard JavaScript objects, this support is an approximation. The resulting objects still feature all the standard Python magic methods and properties, which the JavaScript program can access and call. Furthermore, Python has no concept of private properties; any object state that the Python code has access to can also be accessed by the JavaScript program. While this behaviour is good enough when working with JavaScript programs that expect to be running in this environment, the programs that will be run here are not expecting to run in this environment. Furthermore, they are untrusted; with access to the runtime internals, they could potentially wreak havoc. In order to present a cleaner interface to the JavaScript programs, we instead define JavaScript extensions that, inside a closure, feature a ‘native function’ call. These objects then define proxy functions that call the equivalent functions in the Python implementation. By doing this, we both present genuine JavaScript objects that act like real objects in all ways, and prevent access to the implementation details of the runtime. ## Emulation in CloudPebble The majority of our developers use our web-based development environment, CloudPebble. The QEMU Pebble emulator and PebbleKit JS simulator described above are designed for desktop use, and so would not in themselves be useful to the majority of our developers. Some arrangement therefore had to be made for those developers. We decided to run a cluster of backend servers which would run QEMU on behalf of CloudPebble’s users; CloudPebble would then interact with these servers to handle input and display. ## Displaying the Screen Displaying a remote framebuffer is a common problem; the most common solution to this problem is VNC’s Remote Framebuffer Protocol. QEMU has a VNC server built- in, making this the obvious choice to handle displaying the screen. CloudPebble’s VNC client is based on [noVNC](https://github.com/kanaka/noVNC), an HTML5 JavaScript-based VNC client. Since JavaScript cannot create raw socket connections, noVNC instead connects to the QEMU VNC server via VNC-over-websockets. QEMU already had support for this protocol, but crashed on receiving a connection. We made a minor change to initialisation to resolve this. While it would appear to make sense to use indexed colour instead of 24-bit true colour for our 1-bit display, QEMU does not support this mode. In practice, performance of the true colour display is entirely acceptable, so we did not pursue this optimisation. ## Communicating With the Emulator CloudPebble expects to be able to communicate with the watch to install apps, retrieve logs, take screenshots, etc. With physical watches, this is done by connecting to the phone and communicating over the Pebble WebSocket Protocol (PWP). Due to restrictions on WebSocket connections within local networks, CloudPebble actually connects to an authenticated WebSocket proxy which the phone also connects to. In addition, this communication occurs over bluetooth — but the PebbleKit JS runtime is already connected to the qemu\_serial socket, which can only support one client at a time. We therefore chose to implement PWP in the PebbleKit JS simulator. This neatly the issue with multiple connections to the same socket, closely mimics how the real phone apps behave, and minimises the scope of the changes required to CloudPebble. CloudPebble’s use of a WebSocket proxy provides further opportunity to imitate that layer as well, enabling us to take advantage of the existing authentication mechanisms in CloudPebble. The PebbleKit JS simulator was thus split out to have two ‘runners’; one that provides the standard terminal output (sendings logs to stdout and interacting with the user’s local machine) and one that implements PWP. The WebSocket runner additionally hooks into the low-level send and receive methods in order to provide the message echoing functionality specified by PWP. Since the emulator requires some communication that is not necessary with real phones, there are were some extensions added that are used only by the emulator. However, for the most part, existing code works exactly as before once pointed to the new WebSocket URL. ## Configuration Pages The mechanism previously designed for configuration pages is only usable when running locally. To trigger a configuration page, CloudPebble sends a request using an extension to the PWP. If the PebbleKit JS app implements a configuration page, it receives a response giving it the developer’s intended URL. CloudPebble then inserts a return\_to parameter and opens a new window with the developer’s page. Once the page navigates to the return URL, the page is closed and the configuration data sent back over the WebSocket. Due to restrictions on how windows may communicate, CloudPebble must poll the new window to discover if it has navigated to the return URL. Once the navigation is detected, CloudPebble sends it a message and receives the configuration data in reply, after which the window closes. A further complication is pop-up blockers. These usually only permit window opens in response to direct user action. Since we had to request a URL from the PebbleKit JS app before we could open the window, an active popup blocker will tend to block the window. We worked around this by detecting the failure of the window to open and providing a button to click, which will usually bypass the popup blocker. ## Input Originally, button input from CloudPebble was performed by sending keypresses over VNC directly to QEMU. However, this turned out to cause issues with key- repeat and long button presses. Resolving these issues proved to be difficult, so we instead avoided sending VNC keypresses at all. Instead, another extension was added to the PWP that permitted sending arbitrary PQP messages to the emulator. We then sent packets indicating the state of each button to the emulator via PWP. However, mouse clicks tend to be quicker than Pebble watch button presses, and the time that the buttons appeared to be ‘pressed’ was too short for the firmware to react. To avoid this problem, we rate limited CloudPebble to send state changes no more rapidly than once every 100ms; more rapid changes are queued to be sent later. The channel for sending PQP messages over PWP is also used to set the battery state and toggle bluetooth; in the future, we will also use it to set the accelerometer and compass readings. ## Compass and Accelerometer Sensors Most computers do not have a compass or an accelerometer — and even if they did, it would be impractical to pick up the computer and shake it, tilt it, or rotate it to test apps. To deal with this, we took advantage of a piece of hardware owned by all Pebble owners, and likely most Pebble developers: their phones. When developers want to use their phones to provide sensor data, a six-digit code is generated and stored with the information required to connect to the emulator. The user is prompted to open a short URL (cpbl.io) on their phone and enter the code on that webpage. The code is looked up and, if found, a connection to the emulator is established from the webpage on their phone. The webpage then collects accelerometer and compass data using the [HTML5 DeviceOrientation APIs](http://w3c.github.io/deviceorientation/spec-source-orientation.html) and streams it to the emulator. The generated codes expire a few minutes after being generated. ## Emulator Management CloudPebble must manage multiple emulators on potentially multiple hosts. Management is split between CloudPebble itself and a [controller program](https://github.com/pebble/cloudpebble-qemu-controller) responsible for managing the lifecycle of individual emulator instances. CloudPebble is aware of a pool of emulator hosts. When a user requests an emulator, it picks one at random and requests that its manager spin up an emulator. If this is possible, it returns connection details for the emulator to the client; if not (e.g. because that host has reached capacity), it picks another host and tries again. If no hosts are available a failure message is reported to the client and logged in our analytics system. The manager program selects some unused ports and spawns instances of QEMU and pypkjs configured to work together, and reports back a UUID and public port numbers for the VNC and Pebble WebSocket Protocol servers. The manager then expects to be pinged for that emulator periodically; if too long passes without being pinged or either QEMU or pypkjs fail, the QEMU and pypkjs instances will be terminated. A complication arose when attempting to run this system over the Internet. Some users, especially behind corporate firewalls, cannot make connections to the non-standard ports that the manager was selecting. To avoid this issue, the manager (which runs on the standard HTTPS port 443) can proxy connections to the VNC and PWP websockets. Finally, in order to restrict abuse and provide continuity across client reloads, CloudPebble tracks emulators assigned to users in a Redis database. If a user who already has an emulator requests one and CloudPebble can ping their emulator, they are given the same instance again. A new instance can be requested by explicitly killing the emulator in the CloudPebble UI, or closing the client and waiting for the manager to time out and kill the emulator. * * * --- # Pebble Emulator 1/2 - QEMU for Pebble Source: https://developer.repebble.com/blog/2015/01/30/Development-Of-The-Pebble-Emulator/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 30 January 2015 - Ron Marianetti - [Down the Rabbit Hole](https://developer.repebble.com/blog/tags/down-the-rabbit-hole/) - [Comments](https://developer.repebble.com/blog/2015/01/30/Development-Of-The-Pebble-Emulator.md#comments) This is another in a series of technical articles provided by the members of the Pebble software engineering team. This article describes some recent work done at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short for Quick EMUlator, is a generic, open source machine emulator and virtualizer). > This post is part 1 of 2, and covers the development of the Pebble QEMU emulator itself. Read [_Pebble Emulator 2/2 - JavaScript and CloudPebble_](https://developer.repebble.com/blog/2015/01/30/Pebble-Emulator-JavaScript-Simulation.md) for more information on PebbleKit JS emulation and embedding the emulator in CloudPebble. An early implementation of this emulator was used internally at Pebble over a year ago and was mentioned in a previous article published in April, 2014, [_How Pebble Converted 135,070 Customized Watchfaces for Pebble OS v2.0._](http://appdevelopermagazine.com/1313/2014/4/14/How-Pebble-Converted-135,070-Customized-Watchfaces-For-Pebble-OS-v2.0/) However, it had languished due to lack of attention, and somewhere along the line changes made to the firmware had broke it altogether. An emulator has many benefits, not only for outside developers but also for the internal team: - New software can be loaded, executed, and tested much faster on the emulator than on actual hardware. - It greatly facilitates automated testing, due to the faster speed of execution, convenience, and ease of scripting. - For internal use, the emulator can be built to run firmware images that wouldn’t fit into the limited memory of the real hardware, enabling engineers to develop and test new features with extra debugging features included. - Some tasks, like testing localization changes, can be easily performed in parallel by launching a different emulator instance for each language. [The QEMU open source project](http://wiki.qemu.org/Main_Page) was written by Fabrice Bellard. QEMU is a very flexible and powerful code base capable of emulating a wide variety CPUs, one of which is the ARM Cortex M3 that is inside the STM32F2xx microcontroller used in the Pebble. Building upon the core QEMU project as a base, Andre Beckus created a [fork](http://beckus.github.io/qemu_stm32/) that adds in support for the hardware peripherals of the STM32F1xx series of microcontrollers. Pebble further built upon Andre Beckus’ fork for its emulator and added in support for the STM32F2xx series of microcontrollers as well as the specific hardware components in the Pebble that are outside the microcontroller, like the display and buttons. The result is a full-featured Pebble emulator capable of executing Pebble firmware images and native 3rd party applications. ## Interacting With the Emulator When you launch the Pebble emulator on a host machine you are presented with a window displaying the contents of the Pebble display. You can interact with it using the arrow keys from the keyboard, which act as the 4 buttons on the Pebble (“back”, “up”, “down”, and “select”). You can also load in and run, unmodified, any third-party watchface or watchapp written for the Pebble. A huge part of the value of the Emulator is in the additional features it provides for development and testing purposes, like being able to interact with the Pebble through a terminal and debug it using gdb. These capabilities are not even possible with a standard shipping Pebble. Before the emulator, the only way engineers at Pebble could accomplish this was to use custom “big boards”, which are specially built boards that include the standard Pebble components with the addition of a USB port and associated circuitry. The emulator exposes three socket connections to the outside world. The first, the gdb socket, is built into the base QEMU framework itself and allows one to connect and debug the emulated CPU using gdb. The second, the console socket, is specific to Pebble emulation and is a simple terminal window into which you can see print output and issue commands to the Pebble OS. The third, the qemu channel, is also specific to Pebble emulation and is used for sending Bluetooth traffic and various other hardware sensor information such as the battery level, compass direction, and accelerometer readings. ## The GDB Socket Built into QEMU is a gdb remote server that listens by default on port 1234 for connections from gdb. This remote server implements most of the basic debugging primitives required by gdb including inspecting memory, setting breakpoints, single-stepping, etc. A critical aspect to debugging firmware running on the Pebble is the ability to see what each of the threads in the operating system is doing at any time. For this, gdb provides the “info threads” and related commands like “thread apply all backtrace”, etc. In order to gather the information about the threads though, the gdb remote server needs to understand where the thread information is stored in memory by the remote target, which of course is operating system specific. The built-in gdb remote server implemented by QEMU does not have this knowledge because it is specific to the Pebble. Internally, Pebble uses FreeRTOS for managing threads, mutexes, semaphores, etc. Since the location of this information in memory could easily change in different versions of the Pebble firmware, it did not seem appropriate to incorporate it into QEMU. Instead, we took the approach of creating a proxy process that sits between gdb and the gdb remote server implemented by QEMU. This proxy forwards most generic requests from gdb unmodified to QEMU and returns the responses from QEMU back to gdb. If however it sees a request from gdb that is thread related, it does the handling in the proxy itself. Interpreting the thread command generally involves issuing a series of primitive memory read requests to QEMU to gather the information stored by FreeRTOS. This design ensures that the QEMU code base is isolated from the operating system dependencies of the Pebble firmware that could change from version to version. ## The Console Socket Console input and output has always been built into the Pebble OS but was traditionally only accessible when using one of the engineering “big boards”. There are function calls in Pebble OS for sending output to the console and a simple interpreter and executor that parses input commands. This console output and the available commands are invaluable tools when developing and debugging the Pebble firmware. Routing this console output to a TCP socket leverages a very powerful and flexible feature of QEMU, which is the ability to create up to four virtual serial devices. Each serial device can be associated with a file, pipe, communication port, TCP socket (identified by port number), etc. This capability is exposed through the `-serial` command line option of QEMU. When we launch QEMU to emulate a Pebble, we create one of these serial devices for console use and assign it to a socket. On the emulated Pebble side, the firmware is simply accessing a UART peripheral built into the STM32F2xx and has no knowledge of the external socket connection. In QEMU, the code that emulates that UART device is given a reference to that virtual serial device so that it can ferry data between the two. ## The QEMU Channel Socket The third socket is the Pebble QEMU channel (PQ channel). This channel is used for a number of purposes that are specific to the Pebble running in the emulator. For example, we send data through this channel to give the Pebble custom sensor readings like accelerometer, compass, battery level, etc. We also use this channel to send and receive Bluetooth traffic. We have decided to take the approach of creating custom builds of the firmware for use in the emulator, which frees us from having to make the emulator run the exact same firmware image that runs on an actual hardware. Of course it is advantageous to minimize the differences as much as possible. When it comes to each of the hardware features that needs to be emulated, a judgment call thus has to be made on where to draw the line – try to emulate the hardware exactly or replace some of the low level code in the firmware. Some of the areas in the firmware that we have decided to modify for the emulator are the areas that handle Bluetooth traffic, accelerometer readings, and compass readings, for example. There is some custom code built into the firmware when it is built for QEMU to support the PQ channel. It consists of two components: a fairly generic STM32F2xx UART device driver (called “qemu\_serial”) coupled with some logic to parse out “Pebble QEMU Protocol” (PQP) packets that are sent over this channel and pass them onto the appropriate handler. Every PQP packet is framed with a PQP header containing a packet length and protocol identifier field, which is used to identify the type of data in the packet and the handler for it. ## Bluetooth Traffic In the Pebble OS, there is a communication protocol used which is called, not surprisingly, “Pebble Protocol”. When running on a real Pebble, this protocol sits on top of Bluetooth serial and is used for communication between the phone and the Pebble. For example, this protocol is used to install apps onto the Pebble, get the list of installed apps, set the time and date on the watch, etc. The pebble SDK comes with a command line tool called simply “pebble” which also allows you to leverage this protocol and install watch apps directly from a host computer. When using the pebble tool, the tool is actually sending Pebble protocol packets to your phone over a WebSocket and the Pebble app on the phone then forwards the packets to the Pebble over Bluetooth. On the Pebble side, the Bluetooth stack collects the packet data from the radio and passes it up to a layer of code in Pebble OS that interprets the Pebble Protocol formatted packet and processes it. Rather than try and emulate the Bluetooth radio in the emulator, we have instead decided to replace the entire Bluetooth stack with custom code when building a firmware image for QEMU. As long as this code can pass Pebble protocol packets up, the rest of the firmware is none the wiser whether the data is coming from the Bluetooth radio or not. We chose this approach after noticing that most other emulators (for Android, iOS, etc.) have chosen not to try and emulate Bluetooth using the radio on the host machine. Apparently, this is very difficult to get right and fraught with problems. To support the emulator, we have also modified the pebble tool in the SDK to have a command line option for talking to the emulator. When this mode is used, the pebble tool sends the Pebble Protocol packets to a TCP socket connected to the PQ channel socket of the emulator. Before sending the Pebble Protocol packet to this socket, it is framed with a PQP header with a protocol ID that identifies it as a Pebble protocol packet. ## Accelerometer Data Data for the accelerometer on the Pebble is also sent through the PQ channel. A unique PQP protocol ID is used to identify it as accelerometer data. The pebble tool in the SDK includes a command for sending either a fixed reading or a series of accelerometer readings from a file to the emulator. An accelerometer PQP packet includes a series of one or more accelerometer reading. Each reading consists of three 16-bit integers specifying the amount of force in each of the X, Y and Z-axes. On actual hardware, the accelerometer is accessed over the i2c bus in the STM32F2XX. A series of i2c transactions are issued to set the accelerometer mode and settings and when enough samples are ready in its FIFO, it interrupts the CPU so that the CPU can issue more i2c transactions to read the samples out. The current state of the emulator does not yet have the STM32F2XX i2c peripheral fully implemented and does not have any i2c devices, like the accelerometer, emulated either. This is one area that could definitely be revisited in future versions. For now, we have taken the approach of stubbing out device drivers that use i2c when building the firmware for QEMU and plugging in custom device drivers. Control gets transferred to the custom accelerometer device driver for QEMU whenever a PQP packet arrives with accelerometer data in it. From there, the driver extracts the samples from the packet and saves them to its internal memory. Periodically, the driver sends an event to the system informing it that accelerometer data is available. When the system gets around to processing that event, it calls back into the driver to read the samples. When an application subscribes to the accelerometer, it expects to be woken up periodically with the next set of accelerometer samples. For example, with the default sampling rate of 25Hz and a sample size of 25, it should be woken up once per second and sent 25 samples each time. The QEMU accelerometer driver still maintains this regular wakeup and feed of samples, but if no new data has been received from the PQ channel, it simply repeats the last sample - simulating that the accelerometer reading has not changed. ## Compass Data Data for the compass on the Pebble is also sent through the PQ channel with a unique PQP protocol ID. The pebble tool in the SDK includes a command for sending compass orientation (in degrees) and calibration status. On the Pebble side, we have a custom handler for PQP compass packets that simply extracts the heading and calibration status from the packet and sends an event to the system with this information – which is exactly how the real compass driver hooks into the system. ## Battery Level Data for the battery level on the Pebble is also sent through the PQ channel with a unique PQP protocol ID. The pebble tool in the SDK includes a command for setting the battery percent and the plugged-in status. On the Pebble side, we have a custom handler for PQP battery packets inside a dedicated QEMU battery driver. When a PQP battery packet arrives, the driver looks up the battery voltage corresponding to the requested percent and saves it. This driver provides calls for fetching the voltage, percent and plugged-in status. On real hardware, the battery voltage is read using the analog to digital converter (ADC) peripheral in the STM32F2xx. For now, the emulator takes the approach of stubbing out the entire battery driver but in the future, it might be worth considering emulating the ADC peripheral better and then just feed the PQP packet data into the emulated ADC peripheral. ## Taps On the real hardware, the accelerometer has built-in tap detection logic that runs even when the FIFO based sample-collecting mode is turned off. This tap detection logic is used to support features like the tap to turn on the backlight, etc. Data for the tap detection on the Pebble is also sent through the PQ channel with a unique PQP protocol ID. The pebble tool in the SDK includes a command for sending taps and giving the direction (+/-) and axis (X, Y, or Z). On the Pebble side, we have a custom handler for PQP tap packets that simply sends an event to the system with the tap direction and axis – which is exactly what the hardware tap detection driver normally does. ## Button Presses Button presses can be sent to the emulated Pebble through two different mechanisms. QEMU itself monitors key presses on the host system and we hook into a QEMU keyboard callback method and assert the pins on the emulated STM32F2xx that would be asserted in real hardware depending on what key is pressed. An alternate button pressing mechanism is exposed through the PQ channel and is provided primarily for test automation purposes. But here, in contrast to how we process things like accelerometer data, no special logic needs to be executed in the firmware to handle these button PQP packets. Instead, they are processed entirely on the QEMU side and it directly asserts the appropriate pin(s) on the emulated STM32F2xx. A module in QEMU called “pebble\_control” handles this logic. This module essentially sits between the PQ channel serial device created by QEMU and the UART that we use to send PQP packets to the emulated Pebble. It is always monitoring the traffic on the PQ channel. For some incoming packets, like button packets, the pebble\_control module intercepts and handles them directly rather than passing them onto the UART of the emulated Pebble. To register a button state, it just needs to assert the correct pins on the STM32F2xx depending on which buttons are pressed. The button PQP packet includes the state of each button (pressed or not) so that the pebble\_control module can accurately reflect that state among all the button pins. ## Device Emulation So far, we have mainly just talked about how one interacts with the emulator and sends sensor data to it from the outside world. What exactly is QEMU doing to emulate the hardware? ## QEMU Devices QEMU is structured such that each emulated hardware device (CPU, UART, flash ROM, timer, ADC, RTC, etc.) is mirrored by a separate QEMU device. Typically, each QEMU device is compiled into its own module and the vast majority of these modules have no external entry points other than one that registers the device with a name and a pointer to an initialization method. There are far too many intricacies of QEMU to describe here, but suffice it to say that the interface to each device in QEMU is standardized and generically defined. You can map an address range to a device and also assign a device one or more interrupt callbacks. Where a device would normally assert an interrupt in the real hardware, it simply calls an interrupt callback in QEMU. When the emulated CPU performs a memory access, QEMU looks up to see if that address maps to any of the registered devices and dispatches control to a handler method for that device with the intended address (and data if it is a write). The basic mode of communication between devices in QEMU is through a method called `qemu_set_irq()`. Contrary to its name, this method is not only used for generating interrupts. Rather, it is a general-purpose method that calls a registered callback. One example of how this is used in the Pebble emulation is that we provide a callback to the pin on the emulated STM32F2xx that gets asserted when the vibration is turned on. The callback we provide is a pointer to a method in the display device. This enables the display device to change how it renders the Pebble window in QEMU based on the vibration control. The IRQ callback mechanism is a powerful abstraction because an emulated device does not need to know who or what it is connected to, it simply needs to call the registered callback using `qemu_set_irq()`. ## Pebble Structure We create multiple devices to emulate a Pebble. First of course there is the CPU, which is an ARM v7m variant (already implemented in the base code of QEMU). The STM32F2XX is a microcontroller containing an ARM v7m CPU and a rather extensive set of peripherals including an interrupt controller, memory protection unit (MPU), power control, clock control, real time clock, DMA controller, multiple UARTS and timers, I2C bus, SPI bus, etc. Each of these peripherals is emulated in a separate QEMU device. Many of these peripherals in the STM32F2xx behave similarly with those in the STM32F1xx series of microcontroller implemented in Andre Beckus’ fork of QEMU and so could be extended and enhanced where necessary from his implementations. Outside of the STM32F2xx, we have the Pebble specific devices including the display, storage flash, and buttons and each of these is emulated as a separate QEMU device as well. For the storage flash used in the Pebble, which sits on the SPI bus, we could use a standard flash device already implemented in QEMU. The display and button handling logic however are custom to the Pebble. ## Device Specifics Getting the Pebble emulation up and running required adding some new QEMU devices for peripherals in the STM32F2XX that are not in the STM32F1XX and extending and enhancing some of the existing devices to emulate features that were not yet fully implemented. To give a flavor for the changes that were required, here is a sampling: the DMA device was more fully implemented to support 8 streams of DMA with their associated IRQs; a bug in the NOR flash device was addressed where it was allowing one to change 0’s to 1’s (an actual device can only change 1’s to 0’s when programming); many of the devices were raising an assert for registers that were not implemented yet and support for these registers needed to be added in; the PWR device for the STM32F2xx was added in; wake up timer support was not implemented in the RTC; many devices were not resetting all of their registers correctly in response to a hardware reset, etc. ## Real Time Clock One of the more challenging devices to emulate correctly was the real time clock (RTC) on the STM32F2xx. Our first implementation, for example, suffered from the problem that the emulated Pebble did not keep accurate time, quite a glaring issue for a watch! The first attempt at emulating the RTC device relied on registering a QEMU timer callback that would fire at a regular interval. The QEMU framework provides this timer support and will attempt call your register callback after the requested time elapses. In response to this callback, we would increment the seconds register in the RTC by one, see if any alarm interrupts should fire, etc. The problem with this approach is that there is no guarantee that the QEMU timer callback method will be called at the exact requested time and, depending on the load on the host machine, we were falling behind sometimes multiple minutes in an hour. To address this shortcoming, we modified the timer callback to instead fetch the current host time, convert it to target time, and then modify the registers in the RTC to match that newly computed target time. In case we are advancing the target time by more than one in any specific timer callback, we also need to check if any alarm interrupts were set to fire in that interval. We also update the RTC time based on the host time (and process alarms) whenever a read request comes in from the target for any of the time or calendar registers. Although conceptually simple, there were a number of details that needed to be worked out for this approach. For one, we have to maintain an accurate mapping from host time to target time. Anytime the target modifies the RTC registers, we also have to update the mapping from host to target time. The other complication is that the Pebble does not use the RTC in the “classic” sense. Rather than incrementing the seconds register once per second, the Pebble OS actually runs that RTC at 1024 increments per second. It does this so that it can use the RTC to measure time to millisecond resolution. The emulation of the RTC thus needs to also honor the prescaler register settings of the RTC to get the correct ratio of host time to target time. A further complication arose in the Pebble firmware itself. When the Pebble OS has nothing to do, it will put the CPU into stop mode to save power. Going into stop mode turns off the clock and an RTC alarm is set to wake up the CPU after the desired amount of time has elapsed. On real hardware, if we set the alarm to fire in _N_ milliseconds, we are guaranteed that when we wake up, the RTC will read that it is now _N_ milliseconds later. When running in emulation however, quite often the emulation will fall slightly behind and by the time the emulated target processes the alarm interrupt, the RTC registers will show that more than _N_ milliseconds have elapsed (especially since the RTC time on the target is tied to the host time). Addressing this required modifying some of the glue logic we have around FreeRTOS in order to fix up the tick count and wait time related variables for this possibility. ## UART Performance We rely heavily on the PQ channel to communicate with the emulated pebble. We use it to send 3rd party apps to the emulated pebble from the host machine for example and even for sending firmware updates to the emulated Pebble. The first implementation of the UART device though was giving us only about 1Kbyte/second throughput from the host machine to the emulated Pebble. It turns out that the emulated UART device was telling QEMU to send it only 1 character at a time from the TCP socket. Once it got that character, it would make it available to the emulated target, and once the target read it out, it would tell QEMU it had space for one more character from the socket, etc. To address this, the QEMU UART device implementation was modified to tell QEMU to send it a batch of bytes from the socket at a time, then those bytes were dispatched to the target one at a time as it requested them. This simple change gave us about a 100x improvement in throughput. ## CPU Emulation Issues Running the Pebble OS in QEMU ended up stressing some aspects of the ARM that were not exercised as completely before, and exposed a few holes in the CPU emulation logic that are interesting to note. The ARM processor has two operating modes (handler mode and thread mode) and two different stack pointers it can use (MSP and PSP). In handler mode, it always uses the MSP and in thread mode, it can use either one, depending on the setting of a bit in one of the control registers. It turns out there was a bug in the ARM emulation such that the control bit was being used to determine which stack pointer to use even when in handler mode. The interesting thing about this is that because of the way the Pebble OS runs, this bug would only show up when we tried to launch a 3rd party app for the first time. Another, more subtle bug had to do with interrupt masking. The ARM has a BASEPRI register, which can be used to mask all interrupts below a certain priority, where the priority can be between 1 and 255. This feature was not implemented in QEMU, so even when the Pebble OS was setting BASEPRI to mask off some of the interrupts, that setting was being ignored and any interrupt could still fire. This led to some intermittent and fairly hard to reproduce crashes in the emulated Pebble whose source remained a mystery for quite a while. We discovered an issue that if the emulated CPU was executing a tight infinite loop, that we could not connect using gdb. It turns out that although QEMU creates multiple threads on the host machine, only one is allowed to effectively run at a time. This makes coding in QEMU easier because you don’t have to worry about thread concurrency issues, but it also resulted in the gdb thread not getting a chance to run at all if the thread emulating the CPU had no reason to exit (to read a register from a peripheral device for example). To address this, we modified the CPU emulation logic to break out at least once every few hundred instructions to see if any other thread had work to do. As briefly mentioned earlier, the ARM has a stop mode that is used for power savings and the Pebble OS uses this mode extensively. Going into stop mode turns off the clock on the CPU and most peripherals until an alarm interrupt fires. There was a flaw in the initial implementation of stop mode in the emulator that resulted in any and all interrupts being able to wake up the CPU. The emulated Pebble ran just fine and the user would not notice any problems. However, QEMU ended up using 100% of a CPU on the host machine at all times. When this bug was addressed, the typical CPU load of QEMU went down to about 10%. Addressing this bug was critical for us in order to efficiently host enough QEMU instances on a server farm for use by CloudPebble. Another challenge in the CPU emulation was figuring out how to correctly implement standby mode for the STM32F2xx. In standby mode, all peripherals are powered off and the Pebble sets up the CPU to only wake up when the WKUP pin on the STM32F2xx is asserted. This mode is entered when the user chooses “Shut Down” from the Pebble settings menu or automatically when the battery gets critically low. In most cases, wake up sources for the CPU (like the RTC peripheral, UARTs, timers, etc.) generate an interrupt which first goes to the interrupt controller (NVIC) which is outside the core CPU. The NVIC then figures out the priority of the interrupt and only wakes the CPU if that priority of interrupt is not masked off. The WKUP pin functionality is unique in that it is not a normal interrupt, cannot be masked, and instead of resulting in the execution of an interrupt service routine, it results in a reset of the CPU. Figuring out how to hook this up correctly into QEMU required quite a bit of learning and investigation into the core interrupt handling machinery in QEMU and understanding difference between that and the WKUP functionality. ## Window Dressing A few of the many fun things about working on the emulator had to do with finding creative ways to represent things such as the brightness of the backlight and the status of the vibration output. In the Pebble hardware, a timer peripheral is used to generate a PWM (Pulse Width Modulated) output signal whose duty cycle controls the brightness of the backlight. This allows the Pebble to gradually ramp up and down the brightness of the backlight. In QEMU, the STM32F2xx timer device was enhanced to support registering a QEMU IRQ handler that it would call whenever the PWM output value was changed. In QEMU, when you call an interrupt handler callback using `qemu_set_irq()`, you pass in an integer argument, allowing one to pass a scalar value if need be. A “brightness” interrupt handler callback was added to the Pebble display device and this callback was registered with the timer peripheral. Once hooked up, this enabled the display to be informed whenever the PWM output value changed. The display device implementation was then modified to adjust the brightness (via the RGB color) of the pixels rendered to the display based on the scalar value last sent to its brightness callback. Whenever the Pebble firmware decides to turn on the vibration, it asserts one of the GPIO (General Purpose IO) pins on the STM32F2xx. To implement this feature, a “vibration” callback was added to the display device implementation and this callback was registered with the GPIO device pin that gets asserted by the firmware in order to vibrate. To implement the vibrate, the display device repeatedly re-renders the contents of the Pebble screen to the window offset plus or minus 2 pixels, giving the illusion of a vibrating window. ## Future Work The Pebble emulator has already proven to be a great productivity enhancer in its current state, and has a lot of potential for future enhancements and improvements. It would be interesting for example to investigate how well we can emulate I2C and some of the I2C devices used in the Pebble. Doing this could allow us to use more of the native device drivers in the firmware for things like the accelerometer rather than having to plug in special QEMU based ones. Another area for investigation revolves around Bluetooth. Currently, we replace the entire Bluetooth stack, but another option worth investigating is to run the native Bluetooth stack as-is and just emulate the Bluetooth chip used on the Pebble. This chip communicates over a serial bus to the STM32F2xx and accepts low-level HCI (Host Controller Interface) commands. There is a lot of potential to improve the UI on the QEMU side. Currently, we show only a bare window with the contents of the Pebble display. It would be a great improvement for example to show a status area around this window with clickable buttons. The status area could display helpful debugging information. There is a lot of potential for enhanced debugging and profiling tools. Imagine a way to get a trace of the last _N_ instructions that were executed before a crash, or improved ways to profile execution and see where performance can be improved or power savings realized. ## More Information Continue reading [_Pebble Emulator 2/2 - JavaScript and CloudPebble_](https://developer.repebble.com/blog/2015/01/30/Pebble-Emulator-JavaScript-Simulation.md) for more information on PebbleKit JS emulation and embedding the emulator in CloudPebble. * * * --- # Pebble Smartwatch Emulator in CloudPebble! Source: https://developer.repebble.com/blog/2015/01/22/cloudpebble-pebble-emulator/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 22 January 2015 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [CloudPebble](https://developer.repebble.com/blog/tags/cloudpebble/), [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/01/22/cloudpebble-pebble-emulator.md#comments) We're thrilled to announce the latest addition to CloudPebble: a Pebble smartwatch emulator! ![Love Weather running in CloudPebble](https://developer.repebble.com/assets/images/blog/cloudpebble-emulator-hero.png) To use the emulator, just head over to [CloudPebble](https://cloudpebble.repebble.com/) and run your project. The emulator is now used by default; to install to your phone, change tabs from the Compilation pane: ![Phone / Emulator pane](https://developer.repebble.com/assets/images/blog/cloudpebble-emulator-device-switch.png) The emulator supports running both strictly native apps and those with a PebbleKit JS component, including Pebble.js apps. To interact with the emulator, click on the buttons on the sides, or click in the screen and then use the arrow keys for the buttons. In keyboard mode you can also press X, Y, or Z for a tap in the positive direction, or shift-X, shift-Y, or shift-Z for a tap in the negative direction. You can also adjust the battery state using the settings popover: ![Settings popover](https://developer.repebble.com/assets/images/blog/cloudpebble-emulator-config-popover.png) This pane also lets you shut down the emulated Pebble smartwatch and open your app's configuration page if it has one. However, you will have to make a minor adjustment to your configuration page: you will be passed a query parameter called `return_to`; if you're passed that, you should use it in place of `pebblejs://close#`. For instance, something like this: ``` // Something like this to get query variables.function getQueryParam(variable, default\_) {var query = location.search.substring(1);var vars = query.split('&');for (var i = 0; i \< vars.length; i++) {var pair = vars[i].split('=');if (pair[0] == variable)return decodeURIComponent(pair[1]);}return default\_ || false;}var return\_to = getQueryParam('return\_to', 'pebblejs://close#');document.location = return\_to + encodeURIComponent(JSON.stringify(some\_settings)); ``` We hope this makes developing for the Pebble smartwatch easier than ever! Please [let us know](mailto:cloudpebble@getpebble.com) if it doesn't work with your app. Finally, for those who are interested, we'll have some technical posts soon about how we made this work! * * * --- # Pebble and the Wolfram Cloud Source: https://developer.repebble.com/blog/2015/01/16/Pebble-And-Wolfram/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 16 January 2015 - Arnoud Buzing, Wolfram Research - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2015/01/16/Pebble-And-Wolfram.md#comments) ![](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/wolfram-photo.jpg) > This is a guest blog post by Arnoud Buzing at Wolfram Research about how to use the power of the Wolfram Cloud in a Pebble watchapp. You can find out more information about the Wolfram Cloud in the links posted throughout the post. Enjoy! Greetings! In this post I will describe how to create an app for the [Pebble watch](https://rePebble.com/) that uses the [Wolfram Cloud](http://www.wolfram.com/cloud/). The Wolfram Cloud offers an integrated solution for data storage and knowledge-based computing using the Wolfram Language, a high level programming language. Specifically in this post, I will use so-called 'instant APIs' which allows you to associate an arbitrary Wolfram Language computation with a uniquely generated URL (we call them 'Cloud Objects'). This means that you can use any function from the Wolfram Language [(over 5,000 high level functions)](http://reference.wolfram.com/language/) and any Wolfram|Alpha functionality (including all data from its underlying Wolfram Knowledgebase) and expose this in your Pebble app. So let's get started by making a sample app! First let's look at the user interface of this app and how to navigate to the various elements. The app opens with a splash screen listing its app title and which action to take to continue ('Press SELECT'): ![splash](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble001.png) Upon pressing SELECT (the middle button on the right-hand side of the Pebble watch), you are offered a menu list with seven choices. You can scroll to each choice by using the UP and DOWN buttons (also on the right-hand side of the Pebble watch). Here is a table of images of each menu item and the result window that is shown when a particular menu item is selected (with the SELECT button). | Menu Selection | Result of Computation | | --- | --- | | ![date](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble002.png) | ![date2](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble009.png) | | ![ip](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble003.png) | ![ip2](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble010.png) | | ![location](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble004.png) | ![location2](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble011.png) | | ![temperature](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble005.png) | ![temperature2](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble012.png) | | ![version](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble006.png) | ![version2](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble013.png) | | ![sunset](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble007.png) | ![sunset2](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble014.png) | | ![stock](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble008.png) | ![stock2](https://developer.repebble.com/assets/images/blog/pebble-and-wolfram/pebble015.png) | So now we need to create Wolfram Cloud 'instant API' calls for each of the individual menu items. I will show one example here, the remaining six are similar and the full source code can be obtained from this URL (cloud object): [https://www.wolframcloud.com/objects/c68a118b-ad29-4ebf-b330-d1f4549d8ecb](https://www.wolframcloud.com/objects/c68a118b-ad29-4ebf-b330-d1f4549d8ecb). This utility function creates a named cloud object (using `name`) and an expression you want it to execute, like `DateString[]`: ``` Attributes[CreateCloudObject]={HoldAllComplete}; CreateCloudObject[name_String,expr_]:=Module[{cloudObject,apiFunction}, cloudObject=CloudObject["pebble/"<>name]; apiFunction=APIFunction[{},ExportForm[{"result" ->ToString[expr]}, "JSON"] &]; CloudDeploy[apiFunction, cloudObject, Permissions -> "Public"] ] ``` Then for each menu item you create one cloud 'instant API' function: ``` CreateCloudObject["date",DateString[]] ``` That's all the code that it required on the Wolfram Cloud side. Next, we need to write the app for the Pebble. Pebble provides an online cloud IDE for this called [CloudPebble](https://cloudpebble.repebble.com/). One of the options is the use [Pebble.js](https://pebble.github.io/pebblejs/), a javascript environment which Pebble uses to control a binary application that is executed on the watch. The full javascript source code is included at the bottom of this post. Here are a few key highlights of this implementation. First import the required libraries and set the base URL for all the cloud calls: ``` var ui = require('ui');var ajax = require('ajax');var base = 'https://www.wolframcloud.com/objects/user-7053ce31-817f-4643-aec1-eda27051bba6/pebble/'; ``` Define the splash card (window) and show it on the watch: ``` var main = new ui.Card({title: ' Wolfram',icon: 'images/wolfram.png',subtitle: 'Cloud App',body: 'Press SELECT' });main.show(); ``` This code is part of the SELECT click handler and defines the menu that is shown to the user: ``` var menu = new ui.Menu({sections: [{items: [{ title: 'DateString[]', subtitle: 'get the date' }, { title: '$RequesterAddress', subtitle: 'get the ip' }, { title: '$GeoLocation', subtitle: 'get the location' }, { title: 'WeatherData[]', subtitle: 'get the temperature' }, { title: '$Version', subtitle: 'get the version' },{ title: 'Sunset[]', subtitle: 'get the sunset' }, { title: 'FinancialData', subtitle: 'get a stock' }]}]});menu.show(); ``` This code is executed when the user selects a given menu item: ``` switch(e.itemIndex) {case 0: displayResults( base + 'date', 'DateString[]' ); break;case 1: displayResults( base + 'address', '$RequesterAddress' ); break;case 2: displayResults( base + 'location', '$GeoLocation' ); break;case 3: displayResults( base + 'weather', 'WeatherData[Here,"Temperature"]' ); break;case 4: displayResults( base + 'version', '$Version'); break;case 5: displayResults( base + 'sunset', 'Sunset[]'); break;case 6: displayResults( base + 'stock', 'FinancialData["GE"]'); break;default: console.log('e.itemIndex is out of bounds: ' + e.itemIndex);} ``` The `displayResults` function is a function which shows a temporary 'Loading' card and then retrieves the cloud 'instant API' result from the cloud and displays it: ``` function displayResults(url, title) {var wait = new ui.Card({title: 'Loading...', subtitle: 'Please wait'});wait.show();ajax( { url: url, type:'json' }, // successfunction(json) {var result = new ui.Card({ title: title, body: json.result });result.show();wait.hide();}, // errorfunction(error) {console.log('Error during displayResults');console.log(url);console.log(title);});} ``` And that's it! You can now upload the app to your watch from the Pebble cloud IDE and make calls to the Wolfram Cloud! And it's easy to completely customize it with any Wolfram Language function you find useful. And finally, here is the complete Pebble.js [source code](https://developer.repebble.com/assets/other/wolfram-source.js) for this sample project: ``` var ui = require('ui');var ajax = require('ajax');var base = 'https://www.wolframcloud.com/objects/user-7053ce31-817f-4643-aec1-eda27051bba6/pebble/';var main = new ui.Card({title: ' Wolfram',icon: 'images/wolfram.png',subtitle: 'Cloud App',body: 'Press SELECT'});main.show();main.on('click', 'select', function(e) {var menu = new ui.Menu({sections: [{items: [{ title: 'DateString[]', subtitle: 'get the date' }, { title: '$RequesterAddress', subtitle: 'get the ip' }, { title: '$GeoLocation', subtitle: 'get the location' }, { title: 'WeatherData[]', subtitle: 'get the temperature' }, { title: '$Version', subtitle: 'get the version' },{ title: 'Sunset[]', subtitle: 'get the sunset' }, { title: 'FinancialData', subtitle: 'get a stock' }]}]});menu.show();menu.on('select', function(e) {switch(e.itemIndex) {case 0: displayResults( base + 'date', 'DateString[]' ); break;case 1: displayResults( base + 'address', '$RequesterAddress' ); break;case 2: displayResults( base + 'location', '$GeoLocation' ); break;case 3: displayResults( base + 'weather', 'WeatherData[Here,"Temperature"]' ); break;case 4: displayResults( base + 'version', '$Version'); break;case 5: displayResults( base + 'sunset', 'Sunset[]'); break;case 6: displayResults( base + 'stock', 'FinancialData["GE"]'); break;default: console.log('e.itemIndex is out of bounds: ' + e.itemIndex);} });});function displayResults(url, title) {var wait = new ui.Card({title: 'Loading...', subtitle: 'Please wait'});wait.show();ajax( { url: url, type:'json' }, // successfunction(json) {var result = new ui.Card({ title: title, body: json.result });result.show();wait.hide();}, // errorfunction(error) {console.log('Error during displayResults');console.log(url);console.log(title);});} ``` * * * --- # A New Year of Hackathons! Source: https://developer.repebble.com/blog/2015/01/09/A-New-Year-Of-Hackathons/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 09 January 2015 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/01/09/A-New-Year-Of-Hackathons.md#comments) ![](https://developer.repebble.com/assets/images/blog/010915-hack.jpg) It's a brand new year, which for developers and hackers can mean only one thing - a new year of hackathons! Throughout January and beyond, Pebble will be making appearances at various events (large and small) so if you spot an event near you, be sure to come along. We will be giving away Pebble swag and selling refurbished Pebbles for $50, and will also be on hand to answer any questions you have as well as help you create the best hacks on Pebble. We saw some awesome apps and hacks arise from hackathons in 2014, but we think the best is yet to come! See below for details on the most immediate dates, or the Pebble Developers website [Events page](https://developer.repebble.com/community/events.md) for all our plans for future events. If you are organizing a hacking event that we could attend, be sure to [get in touch](https://developer.repebble.com/contact.md). ## Awesome Community Links Here are some of the things we've seen in the Pebble community recently. If you see something you think should be included, let us know! - Matejdro has [found an interesting way](https://forums.getpebble.com/discussion/19365/android-tasker-app-manager-for-pebble-install-uninstall-pebble-apps-via-tasker) to install Pebble apps from the locker using only a watchapp! - [Neutun Labs](http://neutun.com/) is using Pebble to help detect and measure epileptic seizures. ## Upcoming Events Below is a short list of some of the events we will be attending in January: - [**PennApps at the University of Pennsylvania**](http://2015s.pennapps.com/) - January 16-18th - [**HackTech in Santa Monica, CA**](http://www.hacktech.io/) - January 24-26th - [**UofTHacks at University of Toronto, Canada**](https://uofthacks.com) - January 30-1st. - [**VTHacks at Virginia Tech**](http://vthacks.com/) - February 6-8th. - [**HackIllinois University of Illinois at Urbana-Champaign, IL**](http://www.hackillinois.org/) - February 27-1st March ## We're Hiring! As a rapidly growing company, we're always on the lookout for new talent to grow our various teams. If you or anyone you know has the technical skills, wearable enthusiasm and a taste for the Valley lifestyle, have a look at our [jobs page](https://rePebble.com/jobs) to see a list of open positions. * * * --- # Happy New Year! Source: https://developer.repebble.com/blog/2015/01/03/Happy-New-Year/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 January 2015 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2015/01/03/Happy-New-Year.md#comments) ![](https://developer.repebble.com/assets/images/blog/020115-champ.jpg) Hey Pebble Devs! It's been a great year for us at Pebble as well as all the awesome community developers that come up with ever more surprising and useful apps for Pebble. We would not be [where we are today with over 5000 apps without all the hard work that you guys put into your apps](http://www.fastcompany.com/3040235/app-economy/pebble-passes-5000-smartwatch-apps-how-it-got-there-and-what-comes-next), so **thank you**! We look forward to another amazing year with you in 2015, May your hands always be busy! Alex, Cherie, Chris, Heiko, Katharine, Katherine, Łukasz, Matthew H, Matthew T, Thomas ## Happy New Year! As we enter 2015, we took some time to recap all that we have seen, done and achieved in 2014 in the Pebble Developer Community. The list is extensive, but here are a few of our favorite bits: - [Launch of Pebble SDK 2.0](https://developer.repebble.com/blog/2014/02/03/Pebble-appstore-is-live.md), which introduced PebbleKit JS and allowed all kinds of apps for connecting to the web, or using GPS for navigation. - [CloudPebble](https://cloudpebble.repebble.com/) went official as the recommended web-based Pebble IDE. Since then Katharine has been busy adding many advanced features such as code completion, a UI editor and GitHub integration to make it easier than ever to get creating for Pebble. - We continued to expand the capabilities of Pebbles (old and new!) with new [Compass](https://developer.repebble.com/guides/events-and-services/compass.md), [Background Workers](https://developer.repebble.com/blog/2014/09/30/SDK-2.6-Meetup-More.md) and [Wakeup APIs](https://developer.repebble.com/guides/events-and-services/wakeups.md) to allow apps to work in more relevant and timely ways. - We hosted the [2014 Pebble Developer Retreat](https://developer.repebble.com/community/events/developer-retreat-2014.md) to bring together the top community members for hacking, bonding and innovation on the Pebble platform. Also, who could forget that robot competition? - Pebble [went international](https://developer.repebble.com/guides/tools-and-resources/internationalization.md)! With notifications in 80 languages supported as well as translating the watch firmware to three additional languages, users can experience awesome Pebble apps in more languages and regions than ever before. - Pebble Meetups are popping up around the world. So far the [San Francisco](http://www.meetup.com/PebbleSF/), [London](http://www.meetup.com/PebbleLDN/) and [Paris](http://www.meetup.com/PebbleFR/) groups have launched. We would love to help you start a group in your own city, [contact us for help!](https://developer.repebble.com/community/events.md) ## Pebble Emulator Preview ![qemu](https://developer.repebble.com/assets/images/blog/020115-qemu.png) The Pebble Emulator is almost here, and to celebrate we gave away a version you can try via the [QEMU 2014 Advent Calendar](http://blog.vmsplice.net/2014/12/qemu-advent-calendar-2014-retrospective.html?m=1). This package is limited to the pre-compiled apps, but includes a number of demo apps for you to play around with. You will eventually be able to build and test with this emulator, which is great news for budding developers who do not yet own a Pebble! ## Awesome Community Links Here are some of the things we've seen in the Pebble community recently. If you see something you think should be included, let us know! - Pebble can now use the power of [WolframAlpha](http://community.wolfram.com/groups/-/m/t/406359) thanks to the awesome app created by Arnoud Buzing. With this app you can use your watch to access features of the Wolfram Cloud such as financial data, geolocation and weather information. - Pebble Developer Edwin Finch has [created a playlist of videos](https://forums.getpebble.com/discussion/19027/pebble-development-tutorial-series-on-youtube-by-edwin-finch#latest) aimed at helping new developers get into making apps for the Pebble platform. If you prefer your instruction in video format, be sure to have a look! - [First Pebble Meetup in Paris](http://www.meetup.com/PebbleFR/events/219256327/) on Tuesday January 6th! ## We're Hiring! As a rapidly growing company, we're always on the lookout for new talent to grow our various teams. If you or anyone you know has the technical skills, wearable enthusiasm and a taste for the Valley lifestyle, have a look at our [jobs page](https://rePebble.com/jobs) to see a list of open positions. * * * --- # Send a Smile with Android Actionable Notifications Source: https://developer.repebble.com/blog/2014/12/19/Leverage-Android-Actionable-Notifications/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 19 December 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/12/19/Leverage-Android-Actionable-Notifications.md#comments) ![](https://developer.repebble.com/assets/images/blog/1219-header.jpg) Just a few days ago, we released [beta version 2.3 of our Android Application](https://blog.getpebble.com/2014/12/16/ad-23/) with support for actionable notifications. If you have not tested it already, [enroll in our beta channel](https://developer.repebble.com/blog/2014/06/12/Android-Beta-Channel.md) and try it out for yourself! Notifications have always been a key use case for Pebble, and we are excited by this new feature which is going to change the way you look at notifications. With actionable notifications Pebble not only informs you about relevant events, users can now interact with them and choose from actions you as an Android developer attach to them. When connected to an Android device, Pebble will show all wearable actions, just like any Android Wear device. While supporting wearable notifications is easy we have found that there are still a number of mobile apps who miss the opportunity to extend their reach to the wrist. Don't let your app be one of those! In this post, we will describe what you can do with actions on wearable devices and how to add them to your Android notifications. ## Actionable Notifications Android actionable notifications on the phone were introduced with Android 4.1. Developers specify different actions per notification and users can react to them by pushing a button on the phone's screen. By default those actions are shown in the notification area. You can make those actions visible to wearable devices and you even have the option to collect data from the user before triggering the action. For example, a "Reply" action can offer a list of pre-defined messages that the user can choose from. On Pebble, this includes a long list of nice emojis! To take advantage of actionable notifications on Pebble and Android Wear devices, you need to do two things: - Add support for notification actions in your application - Declare which ones you want to make available on wearable device Let's take a look at how to do this. ## Pushing a Notification to the Watch All notifications start with the Android Notification Builder. A best practice is to use the `NotificationCompat` class from the [Android Support libraries](http://developer.android.com/tools/support-library/) which will automatically handle backwards compatibility issues. For example, action buttons are only available in Android 4.1 and up; the library will automatically make sure your code stays compatible with older Android phones and hide the buttons if necessary. If you have not installed the Android Support Library, you can [follow these instructions](http://developer.android.com/tools/support-library/setup.html) or simply remove the `Compat` suffix to the classes we use in this post. To prepare a notification, you create an instance of a builder and define some properties on it. At the very least, you need an icon, a title and some text: ``` NotificationCompat.Builder builder = new NotificationCompat.Builder(context); builder.setSmallIcon(R.drawable.ic_launcher); builder.setContentTitle("Wearable Pomodoro"); builder.setContentText("Starting the timer ..."); ``` To push the notification, get a hold of the system notification manager: ``` NotificationManager notifManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); ``` And finally send the notification: ``` notifManager.notify(42, builder.build()); ``` > The first parameter is a notification id that you can use to edit or cancel the notification later. Once you give your app the permission to send notifications to your Pebble via the notifications menu in the Pebble Android app, this notification appears with the default _Dismiss_ action. ![A simple notification on an Android device and a Pebble](https://developer.repebble.com/assets/images/blog/1219-an-notif.png) If you press _Dismiss_ on Pebble, the notification will be dismissed on the watch **and** on the phone. This is a nice start, but there's so much more control you can give to your users with custom actions. > While custom actions work for Android 4.1 and above, dismiss actions on Pebble are only visible from Android 4.3 onwards. ## Adding Actions to Notifications Let's add a custom action to our notification. The first step is to define what it will do when it is triggered. Actions can execute any of the standard Android intents (Activity, Service or Broadcast). You prepare the intent, wrap it in a `PendingIntent` instance and it will be triggered if the user selects this action. This is what this looks like with a broadcast intent: ``` // Create a new broadcast intentIntent pauseIntent = new Intent();pauseIntent.setAction(ACTION\_PAUSE);// Wrap the intent into a PendingIntentPendingIntent pausePendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, pauseIntent, 0); ``` > Note here that `ACTION_PAUSE` is simply a string that we defined as a constant in the class: > > ``` > static final String ACTION_PAUSE = "com.getpebble.hacks.wearablepomodoro.PAUSE"; > ``` Now that we have the pending intent, we can create the actual `Notification.Action` instance using an action builder: ``` NotificationCompat.Action pauseAction = new NotificationCompat.Action.Builder(R.drawable.ic_launcher, "Pause", pausePendingIntent).build(); ``` To make this action available on the Android notification view, you can add it directly to the notification: ``` builder.addAction(pauseAction); ``` Finally, **and this is the only _wearable_ specific step** , to make the action available on Pebble and Android Wear, you **must** declare it as available to wearable devices: ``` builder.extend(new NotificationCompat.WearableExtender().addAction(pauseAction)); ``` We now have an action available on Android and on Pebble: ![An action shown on the phone and the watch](https://developer.repebble.com/assets/images/blog/1219-an-pause.png) You can add multiple actions as you want. If you have more than one, Pebble will show a "More..." menu and display all the actions in a list. > The option to "Open on Phone" is not available in this example because we did not define a default action for the notification. It will appear automatically if you do. ## User Input on Action In some cases a simple choice of actions is not enough. You may want to collect some information from the user, for example a reply to a text message. On Pebble, the user will be presented with a list of possible replies that you can customize. Such actions always present a list of emojis, too. In Android lingo, this is an action with support for remote input and it is trivial to set up and configure: ``` String[] excuses = { "Coworker", "Facebook", "Exercise", "Nap", "Phone", "N/A" }; RemoteInput remoteInput = new RemoteInput.Builder(KEY_INTERRUPT_REASON) .setLabel("Reason?") .setChoices(excuses) .build(); NotificationCompat.Action interruptAction = new NotificationCompat.Action.Builder(R.drawable.ic_launcher, "Interrupt", replyPendingIntent) .addRemoteInput(remoteInput) .build(); ``` > Pebble will hide input actions if you set `.setAllowFreeFormInput(false)`. Don't forget to add this action to the WearableExtender to **make it visible on wearable devices** : ``` builder.extend(new Notification.WearableExtender().addAction(pauseAction).addAction(replyAction)); ``` And this is what it looks like on Pebble: ![Action with remote input on Pebble](https://developer.repebble.com/assets/images/blog/1219-an-pebble-interrupt.png) ## Caveat One useful caveat to know about when you are sending notifications to Pebble is that the watch will automatically de-duplicate identical notifications. So if the title and content of your notification is identical to another notification recently displayed, Pebble will not show it. This is very useful in the field to avoid spamming the user when notifications are updated but as a developer you may run into this when you test your app. If you do, just make sure something in your message changes between each notification. ## That's All Folks! As you can see there is nothing magical about actionable notifications – and they are very easy to add to your app. We look forward to more Android apps supporting useful wearable actions and taking advantage of remote input. > **Further Reading on Android Notifications** > > For more information, we suggest you take a look at the [Android Notifications Design Guide](http://developer.android.com/design/patterns/notifications.html) and the associated [developer guide](http://developer.android.com/guide/topics/ui/notifiers/notifications.html). Both are great resources to make the most of notifications! * * * --- # Parlez-vous Pebble? Sprechen sie Pebble? ¿Hablas Pebble? Source: https://developer.repebble.com/blog/2014/12/15/Parlez-vous-Pebble/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 15 December 2014 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/12/15/Parlez-vous-Pebble.md#comments) ![](https://developer.repebble.com/assets/images/blog/1212-world.png) The official Pebble Android and iOS apps have been updated to 2.2 and 2.6 respectively. These updates add support for the French, German and Spanish languages. The Pebble firmware, the mobile apps and the appstore are all translated. ## Localization 101 Now that Pebble speaks multiple languages, your app should recognize the user language and automatically adapt to it. To translate the names of days and months, you only need to tell the system that you support localization. Call `setlocale(LC_ALL, "");` when your app starts and `strftime()` will take care of the rest. If you have more content to translate, `setlocale()` also conveniently returns a string with the user locale! For more information and tips on how to effectively translate your app (including the list of supported languages), read [our new internationalization guide](https://developer.repebble.com/guides/tools-and-resources/internationalization.md). ## Firmware and SDK 2.8.1 Released! ![sdk](https://developer.repebble.com/assets/images/blog/1212-sdk.png) After all the [new features](https://developer.repebble.com/blog/2014/11/28/Firmware-2-8-Android-2-1-Meetups.md) of 2.8 were released, there were unfortunately some bugs that needed squashing as soon as possible. Hence we have released firmware 2.8.1, which will fix these issues, as well as improved iOS BLE pairing and better translations. Simply check your Pebble phone app to update your Pebble. This release also comes with an [updated SDK](https://developer.repebble.com/sdk.md), which now allows non-ASCII characters in the app name, includes `locale.h`, as well as several other app building related issues. As always, check the [changelog](https://developer.repebble.com/sdk/changelogs/2.8.1.md) for all the details. ## CloudPebble Now Available in Chinese! ![cp](https://developer.repebble.com/assets/images/blog/1212-cp.png) Previously supporting English, French, Spanish and German, developers can now build Pebble apps using [CloudPebble](https://cloudpebble.repebble.com/) in Chinese! CloudPebble automatically switches to your browser language but you can also manually switch to any of these languages from the dropdown list at the bottom of the UI. ## Pebble Teaches with Codementor Office Hours ![codementor](https://developer.repebble.com/assets/images/blog/1212-codementor.png) Our lead evangelist Thomas Sarlandie led an online workshop entitled [_Building Your First Pebble App in 1 Hour_](https://www.codementor.io/javascript/tutorial/intro-wearable-development-pebble-watch-apps), which guided attendees in developing their first web-connected Pebble watchapp that connected to a weather service. If you, or anyone you know, wants a quick video introduction to development with Pebble.js on CloudPebble, [this is the video for you!](https://www.codementor.io/javascript/tutorial/intro-wearable-development-pebble-watch-apps) ## PebbleKit iOS Deadline for Christmas Like many companies, Apple is [taking a break from approvals](https://developer.apple.com/news/?id=12082104a) over the holiday break starting on December 18th. This means you should submit your app for whitelisting before **December 16th 20:00 PST** to give us time to process and submit your app before you can submit it on the 18th. ## Win a Pebble Steel for the best Community Contribution of The Month We have opened [the community section](https://developer.repebble.com/community.md) and are so happy to see so many cool contributions pop-up that we have decided to give a Pebble Steel to the best contribution submitted before the end of December. Remember that [you can submit](https://developer.repebble.com/community/submission.md) blog posts, tools, example apps and libraries! We have not figured out exactly how we will pick the winner yet. It will be a mix of community feedback and awesomeness of the contribution! Let us know if you would like to see more of this type of give-away. ## We're Hiring! As a rapidly growing company, we're always on the lookout for new talent to grow our various teams. If you or anyone you know has the technical skills, wearable enthusiasm and a taste for the Valley lifestyle, have a look at our [jobs page](https://www.pebble.com/jobs/) to see a list of open positions. * * * --- # Translate Your Apps, More Fun in SF and more! Source: https://developer.repebble.com/blog/2014/12/05/Translate-Your-Apps/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 05 December 2014 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/12/05/Translate-Your-Apps.md#comments) ![](https://developer.repebble.com/assets/images/blog/1205-chinese.png) Here's another newsletter blog post to keep you up-to-date on the Pebble community happenings. This time we've got stories of success and tips on expanding your app's audience! ## Get Ready For The World Soon we will be supporting English, French, German, Spanish and Chinese, all of which will allow time and date functions to produce information in a localized form. Developers everywhere can begin to add localizations to their apps using the [Internationalization API](https://developer.repebble.com/docs/c/Foundation/Internationalization.md "Internationalization"). What does this mean for you? It means you can make your apps accessible to even more users in even more countries! To read more about localization, tips and tools for translating your apps be sure to read the brand new [Translating Your Apps](https://developer.repebble.com/guides/tools-and-resources/internationalization.md) guide! ## Pebble's First Hackathons in China! In November Pebble sponsored two major hackathons in China. The first was [Hack Shanghai](http://www.hackshanghai.com/) with 250 of the top student developers in the country and the second was [THacks](http://www.thacks.org/), the first student-run hackathon at Tsinghua University in Bejing. Both hackathons were 36 hours of intense hacking on Pebble and many other types of hardware. At Hack Shanghai the second place winner overall was FastReader: a Spritz speed reader. The user can preset certain reading material categories through the cellphone and then Pebble flashes the important words. If the user finds the reading material interesting, then the user can read the full article off the cell phone. Another cool Pebble app was Active Social Matching. Users can preset the characteristics of people they'd like to meet. If two users are physically nearby and with \>80% matching characteristics, then the watch will vibrate. The users then can decide from preset messages on the watch if they want to meet with each other or not. There were a lot of other amazing projects that came out of the event and we're looking forward to seeing more types of global applications in many languages as we come to a hackathon near you! ## Pebble London's First Meetup Was a Hit ![london](https://developer.repebble.com/assets/images/blog/1205-london.png) On the 27th of November we [hosted a meetup](http://www.meetup.com/PebbleLDN/) for London based Pebble developers at Innovation Warehouse, and you guys didn't disappoint! Apart from the special guest appearances from [SwimIO](http://www.swimiomotion.com/), we had over 40 Pebblers attend for food, networking and a generally awesome time. We also had flash talks from anyone who wanted to express themselves, and were subsequently entertained by Jens Elstner for [Keepzer](https://www.keepzer.com/), Tim Williams for [what3words](http://what3words.com/) and Nick Jackson for [Pebble Bike](http://www.pebblebike.com/). Didn't make it? Don't fret! We will be continuing to host meetups in London and other places in the coming weeks and months, so join our [Pebble London Meetup group](http://www.meetup.com/PebbleLDN/) to be notified of upcoming events! ## Pebble SF Returns with Holiday Workshop This coming Thursday December 11th, we will once again be taking over the space at Atlassian in San Francisco, and you're invited! If you're in the area be sure to [sign up on the meetup page](http://www.meetup.com/PebbleSF/events/219036473/), and come along for our workshop on how to build your first Pebble watchface! You can buy Pebbles for loved ones as gifts and create a picture scrolling watchface just for them! Here are some pictures from last month's meetup. If you can't make it this time we host them in SF every month and plan to expand rapidly in more locations! ![atlassian](https://developer.repebble.com/assets/images/blog/1205-atlassian.jpeg) ## Hackateam Connected Challenge Hackathon Want to hack _en Français_? Hackateam is launching its almost four month long [online hackathon](http://www.hackate.am/en/challenges/connected/pages/brief) aimed at creating the best new ideas in connected devices and technologies, and **you can take part!** Teams of up to five members can sign up before the 15th January 2015 and develop a prototype project for the 15th February. After the finals on the 15th March, the top 5 winning teams will receive some of 20,000 euros worth of prizes! ## Awesome Community Links Here are some of the things we've seen in the Pebble community recently. If you see something you think should be included, let us know! - The folks behind [Nightscout (CGM in the Cloud)](http://www.nightscout.info/) have had some great success with their project to monitor glucose levels from their wrists. [Check out their video!](http://vimeo.com/104204413) - Wearable analytics platform Strap have [raised $1.25M in funding](http://blog.straphq.com/2014/12/02/strap-raises-1-25-million-seed-round-led-cincytech/) to expand beyond Pebble and Android Wear to even more platforms. We also have a [blog post](https://developer.repebble.com/blog/2014/07/02/Watch-analytics-with-strap.md) and [Developer Retreat guest presentation](https://www.youtube.com/watch?v=iG8Gnf5CD9s) from them! - Want to see live solar flares from your wrist? Well, now you can with Adam Fourney's [Sunshine watchface](https://forums.getpebble.com/discussion/18279/watch-face-ios-and-android-sunshine-observe-solar-flares-on-your-watch). Images are taken from the SOHO spacecraft in orbit around the sun! # Upcoming Events Want to #MakeAwesomeHappen locally? For a list of upcoming events check out our [events page](https://developer.repebble.com/community/events.md)! **If you want to host a meetup in your area Pebble can sponsor you!** * * * --- # Firmware 2.8, Android 2.1 and Pebble London Meetup! Source: https://developer.repebble.com/blog/2014/11/28/Firmware-2-8-Android-2-1-Meetups/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 28 November 2014 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/11/28/Firmware-2-8-Android-2-1-Meetups.md#comments) ![](https://developer.repebble.com/assets/images/blog/1128-noboxes.jpg) Another newsletter detailing our new releases for the Pebble firmware, Android and iOS apps is reproduced here for your blog reading pleasure. In addition, we also have some exciting news about the Pebble appstore! ## Firmware 2.8 Released We've released the latest version of the Pebble firmware, 2.8! This includes the following features: - Support for receiving notifications in 80 more languages with 351 more characters added to the main system font. This means that the vast majority of users will no longer see boxes in notifications! - Introduced a new [Internationalization API](https://developer.repebble.com/docs/c/Foundation/Internationalization.md "Internationalization") for developers to begin preparing their apps for localization. - More bug fixes and improvements. Full changes can be found on the [Pebble Developers](https://developer.repebble.com/sdk/changelogs/2.8.md) site. ## Pebble Appstore Updated On Monday the Pebble appstore received an update that dramatically increases browsing speed, enabling users to find and try out new apps faster than ever before! Existing users can get the new version by force-closing their Android or iOS apps and re-opening them. ## Android 2.1 Released The new version of the Pebble Android app, 2.1, is now available to all on the [Google Play Store](https://play.google.com/store/apps/details?id=com.getpebble.android). This version is the full release of the beta we mentioned in the last newsletter. At the moment it is only available to users running Android 4.3 (Jelly Bean) and above, but support for earlier versions will be returning soon! In case you missed it, here are the new features: - Rewritten to be faster, more reliable and a better foundation for future new features. - Ability to show all notifications on the watch. Simply pick from a list of your installed apps! - Ability to end in-progress calls. - Lots of other bug fixes and improvements. ## iOS 2.5.2 Released The Pebble iOS app has also received some love with a new version released that includes: - The latest JavaScript Pebble apps available for iOS users in the Pebble appstore. - Bug fixes and stability improvements. ## Awesome Community Links Here are some of the things we've seen in the Pebble community recently. If you see something you think should be included, let us know! - The ["Velocity" watchface by Ycleptic Studios](https://apps.getpebble.com/applications/546b75b8bef790baf90000bb) has a sleek look as well as a cool accelerometer-based watchface configuration solution. - SwimIO updated their ["The Kevin Pacey" swimming app](https://apps.getpebble.com/applications/536d0c4dbcec8f825000026e) to better help you keep track of your swimming progress. - [JavaPay](https://apps.getpebble.com/applications/54527bf9d692a12cfa000081) (formerly PebbleBucks) by Alexsander Akers allows you to use your Starbucks card from your wrist! ## Meetup News ### Pebble Meetups in London! In addition to the meetups in San Francisco, we'll also be organizing events in other countries closer to you, starting with London, UK! At Innovation Warehouse on November 27th you can meet up with other UK based developers and enthusiasts. Register to attend this event on the [Pebble London Meetup group](http://www.meetup.com/PebbleLDN/)! ### Other Upcoming Events Want to #MakeAwesomeHappen locally? Here is a list of events happening soon that Pebble is sponsoring! If you want more details about these events check out our [events page](https://developer.repebble.com/community/events.md)! **December 4th - Codementor Office Hours with Pebble** [https://www.codementor.io/officehours](https://www.codementor.io/officehours) **December 6-7th - RobotsConf at the Omni Amelia Island Resort in Florida, USA** [http://2014.robotsconf.com/](http://2014.robotsconf.com/) **Submit your own meetups on the new [Community Events page](https://developer.repebble.com/community/events.md). If you want to host a meetup in your area Pebble can sponsor you!** * * * --- # New Developer Site, CloudPebble Improvements, New Android Beta and Meetups! Source: https://developer.repebble.com/blog/2014/11/15/New-Developer-Site-CloudPebble-Improvements-Android-Beta-Meetups/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 15 November 2014 - [Chris Lewis](https://developer.repebble.com/blog/authors/chrislewis/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/11/15/New-Developer-Site-CloudPebble-Improvements-Android-Beta-Meetups.md#comments) ![](https://developer.repebble.com/assets/images/blog/1112-rackspace.jpg) There was so much to pack into the last newsletter, we had to make a blog post about it! ## New Developer Site ![devsite](https://developer.repebble.com/assets/images/blog/1112-devsite.png) We've created a new [developer site](https://developer.repebble.com/index.md) to make it much easier to use all the resources available. Have a look around and try out the improved search feature, which will show you all the blog posts, guides and documentation related to your query. As a bonus, we've also improved the Developer Guides and made it easier to find all the great work by the Pebble community. We want submissions of events, example apps, tools and libraries from YOU! Read the [submission instructions](https://developer.repebble.com/community/submission.md) to get started. ## CloudPebble Improvements ![cp-locale](https://developer.repebble.com/assets/images/blog/1112-cp-locale.png) [CloudPebble](https://cloudpebble.repebble.com/) is now available in four languages! Use the drop-down box at the bottom of the page to choose a language. We currently support English, French, German and Spanish. This translation was crowdsourced in less than a day with the help of a group of [awesome community members](https://github.com/pebble/cloudpebble/blob/master/translators.md)! ![cp-autocomplete](https://developer.repebble.com/assets/images/blog/1112-cp-autocomplete.png) CloudPebble also now includes fuzzy code completion, including your own symbols! You can now type your code faster, and be made aware of any mistakes while you type. You can also use this feature to see which SDK functions are available and what arguments they require. ## Android App Beta Testing ![beta](https://developer.repebble.com/assets/images/blog/1112-beta.png) We're beta testing a new version of the Pebble Android app, which you can help us with by signing up to the [beta channel group](https://groups.google.com/forum/#!forum/pebble-android-beta). We've re-written it from the ground up to be more stable and provide a much better base for new features in the future. You can also now choose which notifications you want to see on Pebble - from all your installed apps! If you find any bugs, be sure to let us know using the Support option. **Note that this version is required for use of CloudPebble features such as installation and viewing app logs.** ## PebbleKit Android 2.6 We have also released a new version of PebbleKit Android that contains various improvements and bug fixes. It is backward compatible with older versions in all respects except for data logging, which is not backward compatible with versions of the Pebble Android app before 2.1. You can get this version by updating your build.gradle file in Android Studio to reference this version number, or [download the new .jar file](https://oss.sonatype.org/service/local/repositories/releases/content/com/getpebble/pebblekit/2.6.0/pebblekit-2.6.0-eclipse.jar) for use in Eclipse. The source code is available on the [GitHub repo](https://github.com/pebble/pebble-android-sdk). ## Awesome Community Links Here are some of the things we've seen in the Pebble community recently. If you see something you think should be included, let us know! [How to submit your app on the Pebble appstore (by @thatpatrickguy)](http://www.sitepoint.com/submit-pebble-app-app-store/) [ZSH Completion for the pebble tool (by @iNeal)](https://github.com/Neal/pebble-zsh-completion) [Writing Pebble apps in C++ (by Pebble Forums user Wayne06)](https://forums.getpebble.com/discussion/17349/writing-pebble-apps-in-c-mild-success-one-stumbling-block-resolved) ## Meetups ### Pebble Meetup Next Week in San Francisco! If you're in the SF Bay Area, come to our next Pebble Developer Meetup event on November 20th! Meet other Pebble developers and enthusiasts, see lighting talks by fellow developers and learn how to develop great apps for Pebble. Anyone interested in Pebble development — hardcore or just getting started — is welcome to attend. Register to attend on the [Pebble SF Meetup group](http://www.meetup.com/PebbleSF/)! ### Pebble Meetups in London! In addition to the meetups in San Francisco, we'll also be organizing events in other countries closer to you, starting with London, UK! At Innovation Warehouse on November 27th you can meet up with other UK based developers and enthusiasts. Register to attend this event on the [Pebble London Meetup group](http://www.meetup.com/PebbleLDN/)! ### Other Upcoming Events Want to #MakeAwesomeHappen locally? Here is a list of events happening soon that Pebble is sponsoring! If you want more details about these events check out our [events page](https://developer.repebble.com/community/events.md)! **November 15-16th - Hack Shanghai in New York University, Shanghai, China** [http://www.hackshanghai.com/](http://www.hackshanghai.com/) **December 6-7th - RobotsConf at the Omni Amelia Island Resort in Florida, USA** [http://2014.robotsconf.com/](http://2014.robotsconf.com/) **Submit your own meetups on the new [Community Events page](https://developer.repebble.com/community/events.md). If you want to host a meetup in your area Pebble can sponsor you!** * * * --- # Pebble Developer Retreat 2014 Recap Source: https://developer.repebble.com/blog/2014/11/08/Pebble-Developer-Retreat-2014-Recap/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 08 November 2014 - [Katherine McAuliffe](https://developer.repebble.com/blog/authors/katherine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/11/08/Pebble-Developer-Retreat-2014-Recap.md#comments) Can you believe it's already been a month since the developer retreat? This blog post is long overdue but it was waiting for some very important content so that I could share some highlights from the retreat! I asked the retreat attendees what their favorite presentations/parts of the retreat were and I think these represent some of the best. If you don't agree, then tell me in the comments! Check out the [full developer retreat recap](https://developer.repebble.com/community/events/developer-retreat-2014.md) broken down day by day in the community section of our new developer website! We're still working on a video montage of the robotics competition so that should be on the website soon! For even more detail on what this awesome group of 100 developers from around the world got up to check out our [full schedule](https://developer.repebble.com/community/events/developer-retreat-2014/detailed-schedule.md). ## Retreat Highlights ### Advanced Techniques: Graphics Heiko Behrens and Matthew Hungerford talk about advanced programming techniques for Pebble. This was a series of three presentations wrapped up into one but the videos are in three parts. This talk focused on graphics techniques including run-time dithering, offline dithering, pixel manipulations, and frame-buffer drawing. This talk featured the amiga boing ball dithering demo. [Presentation on SlideShare](http://www.slideshare.net/pebbledev/pebble-dev-retreat2014graphics) ### Battery Life Stuart Harrell, software engineer at Pebble, gives a talk about battery life and how to preserve it. This talk explains the effect of different system functions on battery life and discusses solutions to common battery life problems with apps. [Presentation on SlideShare](http://www.slideshare.net/pebbledev/pebble-devretreat2014-battery) ### Computer History Museum Photo Booth [Photo Booth Photos on Google+](https://plus.google.com/photos/118265358803224468726/albums/6070990535833345361) ### Awards ###### Most \<3 watchapp in appstore Robert Hesse for [Smartwatch+](https://apps.getpebble.com/applications/52f0573d1ac7949119001518) ###### Most \<3 watchface in appstore Daniel Rodríguez Ríncon for [YWeather](https://apps.getpebble.com/applications/52cc44e045ffdd31dd000180) ###### Rocks the Pebble Forums Jon Barlow ([orviwan](https://forums.getpebble.com/profile/7316/orviwan)) ###### Robot Masterminds Winning Team: The Keggers (KEG, FlutterBot, Footbot) ![](https://developer.repebble.com/assets/images/community/events/developer-retreat-2014/robotwinners.png) ##### Best Developer Retreat Hack Eric Oesterle for PEBBLE PEBBLE DISCO for PEBBLE ![](https://developer.repebble.com/assets/images/community/events/developer-retreat-2014/erichat.png) ###### The Pebble Developer Experience team certainly had ourselves a great time and we hope to see you next year! ![](https://developer.repebble.com/assets/images/community/events/developer-retreat-2014/devexpteam.png) * * * --- # Displaying remote images in a Pebble app Source: https://developer.repebble.com/blog/2014/10/29/Displaying-remote-images/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 29 October 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/10/29/Displaying-remote-images.md#comments) > A picture is worth a thousand words. The old adage applies just as well to your Pebble apps! One of the most common requests when [we attend hackathons](https://developer.repebble.com/community/events.md) is "How do I transfer an image from the Internet to my Pebble app?". Today we introduce a new example that demonstrates downloading a PNG file from the Internet and loading it on Pebble. We will also cover how to prepare your images so that they take as little memory as possible and load quickly on Pebble. The code is available in our [pebble-examples github account](https://github.com/pebble-examples/pebble-faces). Continue reading for more details! ## Downloading images to Pebble The first step to display a remote image is to download it onto the phone. To do this we built a simple library that can be easily reused: NetDownload. It is based on [PebbleKit JavaScript](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) and [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage"). The implementation is in [`pebble-js-app.js`](https://github.com/pebble-examples/pebble-faces/blob/master/src/js/pebble-js-app.js) for the JavaScript part and in [`netdownload.c`](https://github.com/pebble-examples/pebble-faces/blob/master/src/netdownload.c) for the C part. Let's walk through the download process: - The C application initializes the library with a call to `netdownload_initialize()`. The library in turns initializes the AppMessage framework. - The [`show_next_image()` function](https://github.com/pebble-examples/pebble-faces/blob/master/src/pebble-faces.c#L25) calls `netdownload_request(char *url)` to initiate a download. This function sends an AppMessage to the JavaScript with two elements. One is the URL to be downloaded and the second one is the maximum transmission size supported by the watch (this is provided by [`app_message_inbox_size_maximum()`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md#app_message_inbox_size_maximum "app\_message\_inbox\_size\_maximum")). - The JavaScript receives this message, tries to download the resource from the Internet (via `downloadBinaryResource()`) and saves it as byte array. - The image is then split into chunks (based on the maximum transmission size) and sent to the watch. The first message contains the total size of the image so the watchapp can allocate a buffer large enough to receive the entire image. We use the JavaScript success and error callbacks to resend chunks as necessary. - After the last chunk of data, we send a special message to tell the app that we are done sending. The app can then check that the size matches what was announced and if everything is ok, it calls the download successful callback that was defined when initializing the library. ## Loading PNG images on Pebble Instead of converting images to the native PBI format of Pebble, this example transmits the image in PNG format to the watch and uses a PNG library to decompress the image and to display it on the screen. The PNG library used is [uPNG by Sean Middleditch and Lode Vandevenne](https://github.com/elanthis/upng). This approach is very convenient for multiple reasons: - It is often easier to generate PNG images on your server instead of the native PBI format of Pebble. - PNG images will be smaller and therefore faster to transfer on the network and over Bluetooth. It does have one drawback though and that is that the PNG library uses approximately 5.5kB of code space. In the NetDownload callback we receive a pointer to a byte-array and its length. We simply pass them to `gbitmap_create_with_png_data()` (provided by `png.h`) and in return we get a [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") that we can display like any other [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") structure. ## Preparing your images Because your PNG file will be transferred to Pebble and loaded into the limited memory available on the watch, it is very important to make sure that the PNG is as small as possible and does not contain useless information. Specifically: - The image should be 144x168 pixels (fullscreen) or smaller - It should be in black and white - It should not contain any metadata (like the author name, gps location of the pictures, etc) To prepare your image, we recommend using Image Magick (careful! Graphics Magick is a different library and does not support some of the options recommended below, it is important to use Image Magick!) ``` convert myimage.png \-adaptive-resize '144x168\>' \-fill '#FFFFFF00' -opaque none \-type Grayscale -colorspace Gray \-colors 2 -depth 1 \-define png:compression-level=9 -define png:compression-strategy=0 \-define png:exclude-chunk=all \myimage.pbl.png ``` Notes: - `-fill #FFFFFF00 -opaque none` makes the transparent pixels white - `-adaptive-resize` with `>` at end means resize only if larger, and maintains aspect ratio - we exclude PNG chunks to reduce size (like when image was made, author) If you want to use [dithering](http://en.wikipedia.org/wiki/Dither) to simulate Gray, you can use this command: ``` convert myimage.png \-adaptive-resize '144x168\>' \-fill '#FFFFFF00' -opaque none \-type Grayscale -colorspace Gray \-black-threshold 30% -white-threshold 70% \-ordered-dither 2x1 \-colors 2 -depth 1 \-define png:compression-level=9 -define png:compression-strategy=0 \-define png:exclude-chunk=all \myimage.pbl.png ``` For more information about PNG on Pebbles, how to optimize memory usage and tips on image processing, please refer to the [Advanced techniques videos](https://developer.repebble.com/community/events/developer-retreat-2014.md) from the Pebble Developer Retreat 2014. ## Connecting the pieces The main C file (`pebble-faces.c`) contains a list of images to load and everytime you shake your wrist it will load the next one. Take a few minutes to test this out and maybe this technique will find its way into your next watchface! * * * --- # FreeRTOS™ Code Revisions - Background Worker Edition Source: https://developer.repebble.com/blog/2014/10/10/FreeRTOS-Modifications-From-Pebble-v2/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 10 October 2014 - [Brad Murray](https://developer.repebble.com/blog/authors/brad/) - [Down the Rabbit Hole](https://developer.repebble.com/blog/tags/down-the-rabbit-hole/) - [Comments](https://developer.repebble.com/blog/2014/10/10/FreeRTOS-Modifications-From-Pebble-v2.md#comments) As part of our commitment to the open source community, Pebble is releasing its recent modifications to the FreeRTOS project. Pebble has made a few minor changes to FreeRTOS to enable Background Workers for PebbleOS 2.6 as well as to make Pebble easier to monitor and debug. The changes are available [as a tarball](http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/dev-portal/FreeRTOS-8.0.0-Pebble.2.tar.gz). Below is a changelog of the modifications since the last time we released our fork of the FreeRTOS code back in May. - Added `UBaseType_t uxQueueGetRecursiveCallCount( QueueHandle_t xMutex ) PRIVILEGED_FUNCTION;` to queue.h - Added `configASSERT_SAFE_TO_CALL_FREERTOS_API();` - Added `configASSERT_SAFE_TO_CALL_FREERTOS_FROMISR_API();` - Added `uintptr_t ulTaskGetStackStart( xTaskHandle xTask );` to task.h - Added `bool vPortInCritical( void );` to port.c - Fixed an issue with vPortStoreTaskMPUSettings that occurred when more than one task wanted to configure MPU regions Pebble would like to again extend its thanks to FreeRTOS and its community. We'll be releasing additional updates as we continue to modify FreeRTOS in the future. If you have any questions, don’t hesitate to [contact us](https://developer.repebble.com/contact.md). * * * --- # New CloudPebble Code Completion Source: https://developer.repebble.com/blog/2014/10/09/CloudPebble-code-completion/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 09 October 2014 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [CloudPebble](https://developer.repebble.com/blog/tags/cloudpebble/) - [Comments](https://developer.repebble.com/blog/2014/10/09/CloudPebble-code-completion.md#comments) CloudPebble today gains significantly enhanced support for code completion, as well as inline syntax checking and the ability to "go to definition". Check it out! In particular, the new code completion now features: - Fuzzy matching! You can now enter abbrevations (e.g. `tlst` for `text_layer_set_text`) and CloudPebble will know what you mean - Complete your own symbols! The code completion is now aware of your own symbols and will include them in its suggestions - Struct members and unions are now suggested as appropriate, instead of suggesting members that do not exist. Real-time sanity checking of your code means we will highlight errors and warnings in red and yellow, respectively; you can hover over the icon on the left to see the complaint. ![](https://developer.repebble.com/assets/images/blog/cloudpebble-inline-warning.png) ![](https://developer.repebble.com/assets/images/blog/cloudpebble-inline-error.png) Note: if you are suddenly seeing red all over a header file it's probably missing a `#include` or some type definitions. Finally, you can now ⌘-click (Mac) or ctrl-click (Windows, Linux) on symbol names to jump to their definitions in your code - for instance, on a `#define` to see its value, or on a struct to see its definition. We appreciate your feedback! Please let us know by commenting here or emailing us at [cloudpebble@getpebble.com](mailto:cloudpebble@getpebble.com). * * * --- # Build background apps with SDK 2.6.1 Source: https://developer.repebble.com/blog/2014/09/30/SDK-2.6-Meetup-More/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 30 September 2014 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/09/30/SDK-2.6-Meetup-More.md#comments) Today we released SDK 2.6.1, which includes support for background workers. This enables great activity tracking apps like Misfit and Jawbone's Up to keep counting in the background — and you can use them too! Head over to our [background worker guide](https://developer.repebble.com/guides/events-and-services/background-worker.md) and read through the [API documentation](https://developer.repebble.com/docs/c/Foundation/AppWorker.md "AppWorker") to find out how. We have also added APIs for direct access to the framebuffer, and to retrieve information on the watch, including its color and firmware version. Browse through [the release notes](https://developer.repebble.com/sdk/changelogs/2.6.md) for full details. ## More Developer News ### Automatic App Updates Coming Very Soon Automatic app updates will be released to users very soon! If you haven't yet made sure your app is ready, especially if you're using persistent storage, take a look at our earlier [app updates blog post](https://developer.repebble.com/blog/2014/08/26/Getting-Ready-for-Automatic-App-Updates.md). ### First Pebble Developer Meetup in San Francisco on Thursday If you're in the SF Bay Area, come to our first Pebble Developer meetup this Thursday! Meet other Pebble developers and enthusiasts over El Tonayense tacos and beer, and learn how to develop great apps for Pebble. Anyone interested in Pebble development — hardcore or just getting started — is welcome to attend. We'll be raffling off three Pebble Steels to attendees. Register to attend on our [shiny new Meetup group](http://www.meetup.com/PebbleSF/events/208002662/). ### Show Off Your App at Apps World in London Pebble will have a booth at [Apps World](https://www.apps-world.net/europe/index.php) in London this November — and you could have a free booth too! ### Other Upcoming Events Want to #MakeAwesomeHappen locally? Here is a list of events happening soon that Pebble is sponsoring! If you want more details about these events check out our [events page](https://developer.repebble.com/events/)! - **October 11th-12th — HackRU in New Brunswick, NJ** [http://www.hackru.org/](http://www.hackru.org/) - **October 17th-19th — BoilerMake in West Lafayette, IN** [http://www.boilermake.org/](http://www.boilermake.org/) - **October 19th-21st — Modev Wearables + Things Hackathon in Washington, DC** [http://www.modevwear.com/](http://www.modevwear.com/) - **October 20th-21st — Internet of Things Conference in San Francisco, CA** [http://iotaconf.com/](http://iotaconf.com/) * * * --- # XDA - Pebble Developer Challenge Winners! Source: https://developer.repebble.com/blog/2014/09/16/Pebble-XDA-Challenge-Winners/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 16 September 2014 - [Katherine McAuliffe](https://developer.repebble.com/blog/authors/katherine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/09/16/Pebble-XDA-Challenge-Winners.md#comments) Almost two months ago the XDA Forums and Pebble set out on a mission to find three amazing apps/watchfaces and their developers. We called it the XDA - Pebble Developer Challenge and last Friday it all came to a close! These three developers have taken the Pebble and truly stretched its limits to create some amazing products and we want to show them to you! The XDA - Pebble Developer Challenge was a 3-week competition - your opportunity to pitch your proposal for the best Pebble app and bring it to life with a Pebble of your own. Of the 20 proposals that we selected for the second phase, the developers of the best three Pebble apps have been invited to attend the 2014 Pebble Developer Retreat in October. We also gave each of them a Pebble Steel to flaunt! There were a lot of really great entries to the second phase of the competition and all of them deserve a quick download! Check out the complete list of apps and watchfaces that were made during the competition on the [XDA forums](http://www.xda-developers.com/android/the-xda-pebble-developer-challenge-voting-has-begun/). ![](https://developer.repebble.com/assets/images/blog/xda-challenge.png) ##### Without further ado, here are the winners of the XDA - Pebble Developer Challenge (in no particular order) | ![](https://developer.repebble.com/assets/images/blog/JanGrunclPhoto.png) | ## Drag Racing #### Author: Jan Gruncl ##### Location: Czech Republic | ![](https://developer.repebble.com/assets/images/blog/DragRacing.png) | In just 4 years, Jan Gruncl has made dozens of Android apps in all categories - indie, contract jobs and corporate (ew!) - mostly for Czech audiences. He started with Pebble by porting the Silly Walk watchface the second day he got his smartwatch, followed by his reported personal favorite app - Notiwatch. Jan has recently switched to videogame development so his challenge entry, Drag Racing, was a great idea to bring those two together. Drag Racing is a genre that one might not expect to be as entertaining as this game is for Pebble! Three buttons are just about right for this kind of game and it's almost as addicting as Cookie Clicker! There's no waiting for gas, no double currency, just a fair game designed to be fun and turbo to boot! Drag Racing can be found on the [Pebble appstore](https://apps.getpebble.com/applications/54033c6886b01c8c15000138). Check out Drag Racing's [development thread](http://forum.xda-developers.com/pebble/pebble-development/competition-drag-racing-t2849592) and [Pebble app on Github](https://github.com/JohnyGQD/drag-racing-pebble) for the evolution of this awesome game! | ![](https://developer.repebble.com/assets/images/blog/RichardGilbertPhoto.png) | ## PebbleWear #### Author: Richard Gilbert ##### Location: Australia | ![](https://developer.repebble.com/assets/images/blog/PebbleWear.png) | Richard Gilbert is an IT student from Griffith University in Australia. He first took an interest in Pebble during its Kickstarter campaign where he created the "Pebble Concepts" website, which allowed people to share their watchface ideas. Since then, he's been active in the Pebble community sharing new ideas, concepts, and developing small apps. PebbleWear was his most ambitious Pebble project yet and we're excited for the possibilities it will bring! Heard of Android Wear? With PebbleWear your notifications are always accessible right below the watch face in this very well done Android Wear clone. Notifications are mirrored exactly as they appear on your phone and are shown as a list of cards packed with glanceable information. The functionality doesn't end there: by pressing the back button you can dismiss a notification (also dismissing it from your phone) and by pressing the select button you can view additional pages and even trigger actions on the notification! PebbleWear can be found on the [Pebble appstore](https://apps.getpebble.com/applications/540a0aeb7c799eb18d000127) and [Google Play store](https://play.google.com/store/apps/details?id=com.richardgg.androidpls). Check out PebbleWear's [development thread](http://forum.xda-developers.com/pebble/pebble-development/competition-pebble-wear-homescreen-t2848326), [Android app on Github](https://github.com/RichardGG/PebbleWearAndroid), and [Pebble app on Github](https://github.com/RichardGG/PebbleWearPebble) for the evolution of this really cool application. | ![](https://developer.repebble.com/assets/images/blog/AlexisRicoPhoto.png) | ### Geek The Time #### Author: Alexis Rico ##### Location: Spain | ![](https://developer.repebble.com/assets/images/blog/GeekTheTime.png) | Alexis Rico, also known as SferaDev, is a tech student who works on the Paranoid Android team. He is a big fan of science fiction and the Star Wars expanded universe. The open source world is his native environment and he really likes improving on a daily basis. He has been working on several projects but really likes the Android experience which is shown in his masterful use of Android and Pebble for Geek The Time! Geek The Time is the ultimate geek watchface for your Pebble! What started as a simple watchface that showed some movies and geek quotes now allows you to read news, check the weather and even receive the current bitcoin value in different currencies. You can read headlines from XDA, TechCrunch, Android Police or even keep updated on your carrier or current IP! This IS an Android only app though, so tough luck iOS users! Geek The Time can be found on the [Pebble appstore](https://apps.getpebble.com/applications/53ff4974c20483149c000152) and [Google Play store](https://play.google.com/store/apps/details?id=com.sferadev.geekthetime.companion). Check out PebbleWear's [development thread](http://forum.xda-developers.com/pebble/pebble-development/xda-challenge-geek-time-t2846738), [Android app on Github](https://github.com/SferaDev/GeekDaTimeCompanion), and [Pebble app on Github](https://github.com/SferaDev/GeekDaTime) for the evolution of this intricate watchface. * * * --- # Building Apps for the Business Traveler Source: https://developer.repebble.com/blog/2014/09/03/Pebble-Concur-Mojio/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 September 2014 - [Katherine McAuliffe](https://developer.repebble.com/blog/authors/katherine/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2014/09/03/Pebble-Concur-Mojio.md#comments) _This is a guest post by Chris Ismael, Senior Developer Evangelist at [Concur](http://bit.ly/DisruptSF). Developers use Concur's APIs to reinvent their travel apps into time and money-saving tools for the business traveler. Concur and Pebble are sponsoring the **[TechCrunch Disrupt Hackathon 2014](http://disruptsfhackathon.challengepost.com/)** in San Francisco. You can also get a shot at $100K prize for the best travel app at [**The Perfect Trip Fund Awards**](https://developer.concur.com/devcon/PerfectTripFundAwards). (Special thanks to **[Ethan Fan](https://twitter.com/ethanyfan) and [Gabi Zuniga](http://about.me/gabiz)** for the source code)._ The Pebble SDK makes it easy to build **Pebble apps that call cloud APIs**. It also facilitates **communication between your Pebble smartwatch app and your smartphone**. Add to this the fact that your **development environment is run from the browser** (no installation horror!) - you know you've got a winner. In this post, we'll touch on the those three aspects of the Pebble SDK by examining the code written by Ethan and Gabi from a previous hackathon. Take a minute to watch their demo below so you can get a context on what the app is about - a Pebble smartwatch that computes and submits car mileage expense. Their app is a re-imagination of the car mileage expense submission by using a combination of the Pebble SDK, Mojio API, and the Concur API. We'll use the diagram below to explain the code in sequence: ![](https://developer.repebble.com/assets/images/blog/PebbleBalsamiq.png) The source code are accessible as gists at: | **Pebble C SDK (C)** | [ExpensePro.c](https://gist.github.com/ismaelc/4c2bcccbe1b8f9fa279b) | | --- | --- | | **PebbleKit JS (Javascript)** | [pebble-js-app.js](https://gist.github.com/ismaelc/44d084346018a367a3bd) | ## Let's get started! 1. **To establish the connection** betweeen your Pebble smartwatch and the Pebble app in your smartphone, we need to set up the **AppMessage API**. This is explained in detail in the **Preparing AppMessage** section of the [Adding Content from the Web](https://developer.repebble.com/getting-started/watchface-tutorial/part3/) of the Pebble Getting Started tutorial. 2. **On the "phone" side** , we also have event handlers set up in [line 170 of pebble-js-app.js](https://gist.github.com/ismaelc/44d084346018a367a3bd#file-pebble-js-app-js-L170), to fire when the smartwatch app is 'ready', and when an `appmessage` is being sent from the watch to the phone. 3. When the business traveler arrives at their destination, they can choose to **submit the trip captured by Pebble and the Mojio API - as a car mileage expense to Concur**. 4. The button press in Step 3 eventually calls [`postToConcur`](https://gist.github.com/ismaelc/44d084346018a367a3bd#file-pebble-js-app-js-L116) to submit the information to the Concur Expense API And that's all there is to it! Now that you have an overview of how the code works, you can now **put this together as Project in the CloudPebble IDE** like this: ![](https://developer.repebble.com/assets/images/blog/cloudpebbleIDE.png) I strongly suggest that you check out [Pebble's Getting Started tutorial](https://developer.repebble.com/getting-started/watchface-tutorial/part1/) where all we've discussed so far, including CloudPebble, are covered in detail. To sum up, the Pebble SDK: - Makes it easy to call cloud APIs - Facilitates communication between your Pebble smartwatch and your smartphone app - Provides a browser-based IDE called CloudPebble that makes coding easier/faster If you have questions, please feel free to email me at [chris.ismael@concur.com](mailto:chris.ismael@concur.com) * * * --- # Getting Ready for Automatic App Updates Source: https://developer.repebble.com/blog/2014/08/26/Getting-Ready-for-Automatic-App-Updates/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 26 August 2014 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/08/26/Getting-Ready-for-Automatic-App-Updates.md#comments) We are pleased to announce that we will soon be automatically updating apps installed from the Pebble appstore. We believe this will be a significant improvement to your ability to get your work into the hands of your users, as well as to the user experience. However, as developers, you need to make sure that your app is ready for these updates. In particular, you will need to make sure that your app conforms to our new version numbering, and that it can tolerate persistent storage left over from old app versions. ## Version Numbers To prepare for automatic app updates, we have imposed new requirements on the format of app version numbers. The new format is: **major.minor** Where minor is optional, and each component is considered independently (so 2.10 is newer than 2.5). For instance, all of the following are valid version codes: - 1.0 - 2.3 - 0.1 - 2 However, neither of the following are: - ~~1.0.0~~ - ~~2.5-beta6~~ We will automatically upgrade the app if the version number from the watch is less than the latest released version on the appstore. If old versions of your app contained a "bugfix" number, we will use a number truncated to the major.minor format — so "1.0.7" will be taken as "1.0". ## Persistent Storage Automatic updates will retain persistent storage between app versions. This ensures that your users will not be frustrated by updates silently deleting their data and settings. However, this puts the onus on you as developers to ensure your app behaves well when presented with an old version of its persistent storage. If your app does not already use persistent storage, you can ignore this section. We recommend that you do this by versioning your persistent storage, and incrementing the version every time its structure changes. The easiest way to do this versioning will be to create a new key in your persistent storage containing the app version. If you have not already versioned your storage, you should simply check for the version key and assume its absence to mean "version zero". Once you know the "storage version" of your app, you can perform some migration to make it match your current format. If the version is too old, or you have multiple "version zero" formats that you cannot otherwise distinguish, you may instead just delete the old keys - but keep in mind that this is a poor user experience, and we recommend avoiding it wherever possible. Ultimately, you might have code that looks something like this: ``` #define STORAGE\_VERSION\_KEY 124 // any previously unused value#define CURRENT\_STORAGE\_VERSION 3// ...static void clear\_persist(void) {// persist\_delete all your keys.}static void read\_v2\_persist(void) {// Read the old v2 format into some appropriate structure}static void read\_v1\_persist(void) {// Read the old v1 format into some appropriate structure.}static void migrate\_persist(void) {uint32\_t version = persist\_read\_int(STORAGE\_VERSION\_KEY); // defaults to 0 if key is missing.if(version \> CURRENT\_STORAGE\_VERSION) {// This is more recent than what we expect; we can't know what happened, so delete itclear\_persist();} else if(version == 2) {read\_v2\_persist();store\_persist();} else if(version == 1) {read\_v1\_persist();store\_persist();} else if(version == 0) {// Again, just delete this - perhaps we have multiple unversioned types, or 0 is just too// long ago to be worth handling.clear\_persist();}} ``` Obviously, that is just an example; you will have to deal with your own situation as appropriate. If you have further questions about getting ready for automatic app updates, let us know! [Contact us](https://developer.repebble.com/contact.md) or [post on our forums](https://forums.getpebble.com/categories/developer-discussion). * * * --- # CloudPebble Graphical UI Editor Source: https://developer.repebble.com/blog/2014/08/08/CloudPebble-Graphical-UI-Editor/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 08 August 2014 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [CloudPebble](https://developer.repebble.com/blog/tags/cloudpebble/) - [Comments](https://developer.repebble.com/blog/2014/08/08/CloudPebble-Graphical-UI-Editor.md#comments) Today we're adding a new feature to [CloudPebble](https://cloudpebble.repebble.com/) that will make it easier than ever to build your watchfaces and watchapps! The new UI Editor will let you graphically build the user interface for your C app; you can then use the generated C to build anything you like. ![](https://developer.repebble.com/assets/images/blog/ib-lead.png) The UIs you build here can be exported from and imported to CloudPebble, and can be used both in CloudPebble and with the native SDK. ## Getting Started The easiest way to get started with the UI Editor is to create a new, empty C project in [CloudPebble](https://cloudpebble.repebble.com/). To create your first window, click "Add New" next to "Source Files", select "Window Layout", and name your new window: ![](https://developer.repebble.com/assets/images/blog/ib-new-window.png) This will create two new files, `your_window.c` and `your_window.h`. You will build your user interface in `your_window.c`. Click on the "Toolkit", then select some layers (e.g. [`TextLayers`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer")) to add. You can drag these UI elements around on the canvas and set proprties such as the text font or colour from the Properties tab to the right. We recommend changing the auto-generated "Layer ID" fields to something more obvious, as this is what the variables in the generated code will be called. When your UI is ready you can add functionality by clicking the pencil and ruler on the far right of the window and adding code in and around the `show_your_window` and `hide_your_window` functions. Note that you should not change the code between the comments; nor should you change the comments themselves. When you want to display your window you will have to set up something to show it — this is what the `show_your_window` function is for. Create a new C file called `main.c` and fill it with something like this: ``` #include \#include "your\_window.h"int main() {show\_your\_window();app\_event\_loop();return 0;} ``` Of course, you should always replace `your_window` in these examples with whatever you called your window. Make sure you have saved everything, then go ahead and build your project! ## Going Further Beyond simply displaying text, you can also upload fonts or black and white images and use those, too. Just add them as resources as you usually would; they will become available in the appropriate dropdowns for [TextLayers](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"), [BitmapLayers](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") and [ActionBarLayers](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer")). You can create multiple windows and register click handlers on those windows to move between them; take a look at [our user interface guide](https://developer.repebble.com/guides/user-interfaces.md) for details on how to do that. You can show new windows by including the `some_window.h` file and calling the relevant `show_some_window` function. Note, however, that single window cannot be reused again in the window stack. If you want to add layers to your window that the UI editor does not support, you can still use the editor for the rest of the UI; then add code to create the extra elements in the `show_your_window` function. And remember: this is just the first iteration! We plan to regularly enhance the UI editor to expand its capabilities. We would appreciate your feedback! If you have any comments, send them to [cloudpebble@getpebble.com](mailto:cloudpebble@getpebble.com) or post them [on our forum](https://forums.getpebble.com/categories/developer-discussion). If you want to go further still, come visit us! We recently [announced our second annual Developer Retreat](https://developer.repebble.com/events/developer-retreat-2014/) and applications to attend are now open. * * * --- # XDA Forums - Pebble Developer Challenge Source: https://developer.repebble.com/blog/2014/07/21/Pebble-XDA-Challenge/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 21 July 2014 - [Katherine McAuliffe](https://developer.repebble.com/blog/authors/katherine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/07/21/Pebble-XDA-Challenge.md#comments) Can you #MakeAwesomeHappen? Your app idea could win a Pebble and a trip to our 2nd Annual Pebble Developer Retreat in Mountain View, CA! The XDA - Pebble Developer Challenge is a 3-week competition - your opportunity to pitch your proposal for the best Pebble app and bring it to life with a Pebble of your own. We'll be awarding Pebbles to the 20 best app proposals to give even first time developers a chance to work on our platform. Of the 20 proposals that we select the best 3 Pebble apps created will be invited to attend the 2014 Pebble Developer Retreat in October and we'll give them each a Pebble Steel. View the complete details on the [XDA forums](http://www.xda-developers.com/android/xda-pebble-developer-challenge/) ! ![](https://developer.repebble.com/assets/images/blog/xda-challenge.png) Here's how the competition is going to go: #### Round 1 (July 21 - August 15, 2014): 1. Developers around the world are invited to submit a proposal for an app they want to build with Pebble's SDK 2 before August 1, 2014 (5:00pm Pacific Time). 2. A panel of judges from both XDA and Pebble will select 20 Finalists, based on the proposals innovation and originality. 3. The 20 Finalists will each receive one Pebble smartwatch to develop their apps on. #### Round 2 (August 15 - September 12, 2014): 1. Finalists have 3 weeks to develop their apps and submit them before September 5, 2014 (5:00pm Pacific Time) for Grand Prize consideration. 2. A panel of judges from both XDA and Pebble will select 2 Grand Prize winners. 3. Via an open voting system, the public will vote for its favorite app among the 20 Finalists and select the Popular Choice Grand Prize winner. 4. All 3 Grand Prize winners will receive an exclusive, all-expenses-paid invitation to Pebble's 2014 Developer Retreat, one Pebble Steel, and promotional support for their winning apps. Grand prize and Popular Choice award winners will be announced on September 12th, 2014. #### Prizes ##### App Idea Finalists (20) - Pebble Smartwatch ##### Grand Prize Winners (3) - Pebble Steel Smartwatch - Pebble Developer Retreat 2014 invitation with all expenses paid #### Important Dates - **Challenge Idea Submission Period:** July 21, 2014 (10:00am Pacific Time) – August 1, 2014 (5:00pm Pacific Time) - **Idea Selection Period:** August 4, 2014 (10:00am Pacific Time) – August 7, 2014 (5:00pm Pacific Time) - Idea Selection will be done by Pebble and XDA. - **Pebble Shipment Date:** August 8, 2014 - **Challenge Development Period:** August 15, 2014 (10:00am Pacific Time) – September 5, 2014 (5:00pm Pacific Time) - **App Submission Date:** on or before September 5, 2014 (5:00pm Pacific Time) - **Finalist Voting Period:** September 8, 2014 (10:00am Pacific Time) – September 12, 2014 (1:00pm Pacific Time) - Public Finalist Voting will take place on the XDA Website and will be open only to registered XDA users. - XDA will choose 1 grand finalist and Pebble will choose 1 grand finalist. - **All Winners Announced:** On or around September 12, 2014 (2:00pm Pacific Time) Please read the full [Official Rules](https://developer.repebble.com/xda-challenge/official-rules.html) for more details. * * * --- # Watch Analytics with Strap Source: https://developer.repebble.com/blog/2014/07/02/Watch-analytics-with-strap/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 02 July 2014 - [Alex Lin](https://developer.repebble.com/blog/authors/alex/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2014/07/02/Watch-analytics-with-strap.md#comments) > We'd like to share a blog post from our friends at [Strap](https://www.straphq.com/) on how to add analytics to your Pebble app. Strap is an analytics platform targeted at wearables and helps to gather valuable metrics to help developers with their projects. We hope you find this valuable and follow the links below to try it out: There are an increasing number of users downloading your Pebble watchfaces and watchapps, but there is really no good way for you to figure out where those users are coming from, how long they stay, and what they do with your app. You can now gain these insights and more with Strap Metrics, the world's first analytics platform built for wearable developers. Strap Metrics has spent the past couple of months in private beta, and we are excited to go public today with support for both PebbleKit JS apps and Pebble companion apps on iOS and Android. Just like traditional web and mobile analytics platforms, there is an integration step which requires you to include our API in your project. By default, you can include the API and call a couple of init functions to track things like sessions (with duration), unique users, geographic location, device battery performance, user agents, and activity. You can take this a step further by logging events at the C level to paint a complete picture of what users are doing inside of your application. To demonstrate a simple use case, I'll walk you through integration with a Pebble JS watch app that I wrote a while back. Watch3words is the official Pebble app for [What3Words](http://www.what3words.com), an awesome platform that lets you share your precise location in just three words. This is a fairly typical Pebble JS app - it communicates via AppMessage and AppSync to interact with the REST service provided by What3Words. Note: If you have a companion app for iOS or Android, we've got you covered! We have [quick start guides](https://github.com/strap/strap_sdk_pebble/tree/master/companion) for those platforms on our developer dashboard. Before you get started, signup for an account at [https://www.straphq.com/register](https://www.straphq.com/register) - it's free without limits during our public beta! ##### 1) Create an app on the Strap dashboard. ![1](https://developer.repebble.com/assets/images/blog/strap1.png) ##### 2) Make note of your app ID. ![2](https://developer.repebble.com/assets/images/blog/strap2.png) ##### 3) [Download the Strap Metrics Pebble SDK from Github.](https://github.com/strap/strap_sdk_pebble) ##### 4) Add the C source to your project. _The easy way:_ Run this from the "src" directory: ``` curl http://pebble-install.straphq.com | sh ``` _Or manually:_ Copy /src/c to a "strap" directory in your project. ##### 5) Add the JS API to your JavaScript code In the SDK you'll find a sample pebble-js-app.js which contains the API. Copy the API portion of the code to the top of your js file. ![3](https://developer.repebble.com/assets/images/blog/strap3.png) ##### 6) Add the AppMessage event handler to your JavaScript code Also in the sample pebble-js-app.js is an AppMessage event handler block. If you already have an AppMessage event handler, just copy the relevant Strap portion into it. Make sure you change the app\_id to the one you created in Step 1. Pebble.addEventListener("appmessage"), ``` Pebble.addEventListener("appmessage",function(e) {//console.log("appmessage: " + JSON.stringify(e.payload));// Strap API: Developer updates these parameters to fitvar strap\_params = {app\_id: "xyzabc123abcxyz",resolution: "144x168",useragent: "PEBBLE/2.0"};// -------------------------//Strap API inclusion in appmessage//This allows Strap to process Strap-related messages//DO NOT EDIT// -------------------------if(strap\_api\_can\_handle\_msg(e.payload)) {clearTimeout(strap\_api\_timer\_send);var params = strap\_api\_clone(strap\_params);strap\_api\_log(e.payload, 200, params);strap\_api\_timer\_send = setTimeout(function() {strap\_api\_log({}, 0, params);}, 10 \* 1000);}// -------------------------}); ``` ##### 7) Add the C API to your C code Include strap.h in any of your source that contains actions you want to track in Strap. At a minimum, you must include the Strap header and call the init functions in the next step for Strap to track anything. Make sure you correct the path relative to your source if needed. ``` #include "strap/strap.h" ``` ##### 8) Initialize Strap in your C code In a typical Pebble pattern, your main() calls an init() and deinit() function. Here, you'll need to include the strap\_init() and strap\_deinit() functions, respectively. **Important: Your code must call app\_message\_open for Strap to communicate!** Here is a sample init function with the Strap init calls. ``` 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,});const bool animated = true;window\_stack\_push(window, animated);app\_message\_register\_inbox\_received(in\_received\_handler);int in\_size = app\_message\_inbox\_size\_maximum();int out\_size = app\_message\_outbox\_size\_maximum();// required for Strap to communicate! app\_message\_open(in\_size, out\_size);// initialize strap!strap\_init();}static void deinit(void) {// unload strap on deinit!strap\_deinit();window\_destroy(window);}int main(void) {init();app\_event\_loop();deinit();} ``` ##### 9) Add Strap API calls to track events (optional, but recommended) We recommend sending events in a simple URI format. ``` static void select_button_click_handler(ClickRecognizerRef recognizer, void *context) { // do something on your Pebble strap_log_event("/select-button-event"); } ``` If you made it this far, congrats! You've successfully integrated Strap into your Pebble application. We'll start crunching the numbers as data starts to flow into Strap, and you'll be seeing reports on the dashboard in about an hour. We have tested Strap in a variety of app configurations, but your feedback is extremely important to us in this beta period! If you have any questions, concerns, or problems with Strap Metrics, please let us know. You can [open an issue on GitHub](https://github.com/strap/strap_sdk_pebble/issues), visit our community support portal at [http://strap.uservoice.com](http://strap.uservoice.com), email us at [support@straphq.com](mailto:support@straphq.com), or tweet us [@getstrap](https://www.google.com/url?q=http://www.twitter.com/getstrap). * * * --- # Create Your Own Api With Kimono Source: https://developer.repebble.com/blog/2014/06/19/Create-your-own-api-with-KimonoLabs/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 19 June 2014 - [Alex Lin](https://developer.repebble.com/blog/authors/alex/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/06/19/Create-your-own-api-with-KimonoLabs.md#comments) We'd like to share with you a blog post written by our friends at KimonoLabs. Their service allows you to build a JSON API from any website. In this guest- post, they show you how to create a stocker tracker watchapp in less than 10 minutes! This was originally posted here ([http://blog.kimonolabs.com/](http://blog.kimonolabs.com/)). ### Step 1 - Create a Kimono API to get the data If you haven’t done so already, sign up for a free account at KimonoLabs. Once you've signed up, drag the kimonify bookmarklet onto your browser's bookmarks bar: ([http://www.kimonolabs.com/signup](http://www.kimonolabs.com/signup)) ![1](https://developer.repebble.com/assets/images/blog/kimono1.png) First we need to get the data that powers our app, which is simple and painless with Kimono. Lets head over to CNN Money to get some data. ([http://money.cnn.com/data/markets/](http://money.cnn.com/data/markets/)) ![2](https://developer.repebble.com/assets/images/blog/kimono2.png) Launch kimono by clicking on the kimono bookmarklet. The kimono toolbar will appear at the top of the page, making data selectable. Let’s extract the stock tickers, names, stock prices, and the % change for the top 5 stocks. Click on the data you want and add new properties by clicking the grey “+” (e.g., stock price). ![3](https://developer.repebble.com/assets/images/blog/kimono3.png) ![4](https://developer.repebble.com/assets/images/blog/kimono4.png) ![5](https://developer.repebble.com/assets/images/blog/kimono5.png) Click on the data model view (stacked bar icon in the top right) to see how your data is organized and name each property. ![6](https://developer.repebble.com/assets/images/blog/kimono6.png) Then click on the data preview view (inverse carrot icon to the right of the data model icon) to preview your data in json or csv. ![7](https://developer.repebble.com/assets/images/blog/kimono7.png) This looks good, so click DONE, name your API, set a schedule and kimono will create an API. ![8](https://developer.repebble.com/assets/images/blog/kimono8.png) ![9](https://developer.repebble.com/assets/images/blog/kimono9.png) Click the link to see detail on your new API. ![10](https://developer.repebble.com/assets/images/blog/kimono10.png) Click on the JSON endpoint to check out the data. ![11](https://developer.repebble.com/assets/images/blog/kimono11.png) Let's copy the url to this JSON endpoint. Go ahead and open up another tab for the next steps. ### Step 2 - CloudPebble If you haven’t done so already, sign up for a Pebble developer account: ([https://auth.getpebble.com//users/sign\_up](https://auth.getpebble.com//users/sign_up)). Also make sure you have the Pebble app installed on your phone, your Pebble smart watch is paired with your phone, and Pebble developer mode is enabled. (/2 /getting-started/developer-connection/) Set up a new project in the CloudPebble development environment ([https://cloudpebble.repebble.com/](https://cloudpebble.repebble.com/)), making sure that the Project Type is set to SimplyJs. This will allow us to use JavaScript to create our Pebble app, writing code in the CloudPebble in-browser IDE! ![12](https://developer.repebble.com/assets/images/blog/kimono12.png) ![13](https://developer.repebble.com/assets/images/blog/kimono13.png) Go ahead and click the the automatically created app.js file. Delete its contents and paste the following code into the file, replacing \< your\_kimono\_api\_url \> with the url we got from our Kimono API: ![14](https://developer.repebble.com/assets/images/blog/cpkimono.png) Here is the code: ``` var getData = function(){console.log("getting data");ajax({url: '\', type: 'json'},function(data) {console.log("gotdata: " + JSON.stringify(data));var output = '';data.results.collection1.forEach(function(element, index){//get the company title - limited to 10 charactervar companyTitle = element.name;if(companyTitle.length \< 10) companyTitle = companyTitle.substring(0, 10) + '...';//append this to the outputoutput += companyTitle +'\n' + '$' + element.price+'\n' + element.change+'\n\n';});simply.setText({body: output});},function(error) {console.log("Got error: " + JSON.stringify(error));simply.title(error.status);simply.body(error.error);});};console.log("Loading...");// Initialize the screensimply.scrollable(true);simply.title("Loading...");// Start getting the datagetData(); ``` Lets go through a quick explanation of the code. ``` ajax({url: '\', type: 'json'},function(data) {console.log("gotdata: " + JSON.stringify(data));var output = '';data.results.collection1.forEach(function(element, index){//get the company title - limited to 10 charactervar companyTitle = element.name;if(companyTitle.length \< 10) companyTitle = companyTitle.substring(0, 10) + '...';//append this to the outputoutput += companyTitle +'\n' + '$' + element.price+'\n' + element.change+'\n\n';}); ``` This bit of code makes an ajax request to our Kimono api, getting a JSON response. Once we receive that data, we go ahead and process it in the callback. Looping through each company, we pull out the company name (shortened to 10 characters), the company’s current stock price, and the % change in the company’s stock price. ``` simply.setText({body: output}); ``` All this data is then outputted to the screen on our pebble. This functionality is all wrapped up in a function called getData. ``` simply.scrollable(true); ``` This sets a property to make the app scrollable. ``` getData(); ``` Then we simply call our getData function. ![15](https://developer.repebble.com/assets/images/blog/kimono15.png) ![16](https://developer.repebble.com/assets/images/blog/kimono16.png) Hit play and watch your app run. ![17](https://developer.repebble.com/assets/images/blog/kimono17.jpeg) And thats all folks, in just a few minutes we were able to create a stock tracker for Pebble with live data! * * * --- # Pebble SDK Tutorial (Part 2) Source: https://developer.repebble.com/blog/2014/06/18/Your-First-Watchface/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 18 June 2014 - Chris Lewis ## Your First Watchface ### Previous Tutorial Parts [Pebble SDK Tutorial (Part 1): Your First Watchapp](https://developer.repebble.com/blog/2014/06/10/Pebble-SDK-Tutorial-1.md) ### Introduction This is the second in a series of articles aimed at helping developers new and experienced begin creating their own watchapps. In this section we will be using the [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") to display the current time, which is the basis of all great watchfaces. After this, we will use [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap")s and [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer")s to improve the aesthetics of our watchface. By the end of this section, our app will look like this: ![final](https://developer.repebble.com/assets/images/blog/tut_2_frame_added.png "final") ### Getting Started To begin creating this watchface, we will be using the [source code](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) from the last post as a starting point, which you can import into [CloudPebble](https://cloudpebble.repebble.com/) as a new project [here](https://cloudpebble.net/ide/gist/a911f0b053260f209390). As you may have noticed, the current watchapp is started from the Pebble main menu, and not on the watchface carousel like other watchfaces. To change this, go to 'Settings' in your CloudPebble project and change the 'App Kind' to 'Watchface'. This will cause the app to behave in the same way as any other watchface. If you were using the native SDK, this change would be done in the `appinfo.json` file. Once this is done, we will modify the main C file to prepare it for showing the time. Remove the call to [`text_layer_set_text()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text "text\_layer\_set\_text") to prevent the watchface showing anything irrelevant before the time is shown. For reference, `window_load()` should now look like this: ``` void window\_load(Window \*window){//We will add the creation of the Window's elements here soon!g\_text\_layer = text\_layer\_create(GRect(0, 0, 144, 168));text\_layer\_set\_background\_color(g\_text\_layer, GColorClear);text\_layer\_set\_text\_color(g\_text\_layer, GColorBlack);layer\_add\_child(window\_get\_root\_layer(window), text\_layer\_get\_layer(g\_text\_layer));} ``` ### Telling the Time Now we can use the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") we created earlier to display the current time, which is provided to us once we register a [`TickHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler "TickHandler") with the system. The first step to do this is to create an empty function in the format dictated by the [`TickHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TickHandler "TickHandler") documentation page. This is best placed before it will be used, which is above `init()`: ``` void tick\_handler(struct tm \*tick\_time, TimeUnits units\_changed){} ``` This function is called by the system at a fixed tick rate depending on the [`TimeUnits`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#TimeUnits "TimeUnits") value supplied when we register the handler. Let's do this now in `init()` before [`window_stack_push()`](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_push "window\_stack\_push"), using the [`MINUTE_UNIT`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md#MINUTE_UNIT "MINUTE\_UNIT") value to get an update every minute change: ``` tick\_timer\_service\_subscribe(MINUTE\_UNIT, (TickHandler)tick\_handler); ``` Now that we have subscribed to the [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService"), we can add code to the `tick_handler()` function to use the time information provided in the `tick_time` parameter to update the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). The data stored within this structure follows the [ctime struct tm format](http://www.cplusplus.com/reference/ctime/tm/). This means that we can access the current hour by using `tick_time->tm_hour` and the current minute by using `tick_time->tm_min`, for example. Instead, we will use a function called `strftime()` that uses the `tick_time` parameter to write a time-formatted string into a buffer we provide, called `buffer`. Therefore the new tick handler will look like this: ``` void tick\_handler(struct tm \*tick\_time, TimeUnits units\_changed){//Allocate long-lived storage (required by TextLayer)static char buffer[] = "00:00";//Write the time to the buffer in a safe mannerstrftime(buffer, sizeof("00:00"), "%H:%M", tick\_time);//Set the TextLayer to display the buffertext\_layer\_set\_text(g\_text\_layer, buffer);} ``` Now every time a minute passes `tick_handler()` will create a new string in the buffer and display it in the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). This is the basis of every watchface. However, the text contained in the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is difficult to read because the default system font is very samll, so we will change some of the layout properties to better suit its purpose. Modify `window_load()` to add the relevant [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") functions like so: ``` void window\_load(Window \*window){//Create the TextLayerg\_text\_layer = text\_layer\_create(GRect(0, 59, 144, 50));text\_layer\_set\_background\_color(g\_text\_layer, GColorClear);text\_layer\_set\_text\_color(g\_text\_layer, GColorBlack);//Improve the layout to be more like a watchfacetext\_layer\_set\_font(g\_text\_layer, fonts\_get\_system\_font(FONT\_KEY\_BITHAM\_42\_BOLD));text\_layer\_set\_text\_alignment(g\_text\_layer, GTextAlignmentCenter);layer\_add\_child(window\_get\_root\_layer(window), text\_layer\_get\_layer(g\_text\_layer));} ``` Now the watchface should look more like a classic watchface with a larger font and centered text: ![largetext](https://developer.repebble.com/assets/images/blog/tut_2_largetext.png "large text") ### Adding Bitmaps The next logical way to improve the watchface is to add some complementary images. These take the form of [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap")s, and are referenced by a `RESOURCE_ID` specified in CloudPebble Settings or `appinfo.json` when working with the native SDK. The first bitmap we wil add will be a smart border for the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"), shown for you to use below: ![frame](https://developer.repebble.com/assets/images/blog/tut_2_frame.png "frame") To add this image to our project in CloudPebble, click "Add New" next to Resources, navigate to your stored copy of the image above and give it an ID such as "FRAME", then click Save. Once this is done, we can add it to the watchface. The first step is to declare two new global items: A [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") to hold the loaded image and a [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") to show it. Add these to the top of the file, alongside the existing global variables: ``` GBitmap \*g\_frame\_bitmap;BitmapLayer \*g\_frame\_layer; ``` The next step is to allocate the [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap"), show it using the [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") and add it as a child of the main [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer")s are drawn in the order they are added to their parent, so be sure to add the [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") **before** the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). This will ensure that the time is drawn on top of the image, and not the other way around: ``` //Create and add the imageg\_frame\_bitmap = gbitmap\_create\_with\_resource(RESOURCE\_ID\_FRAME);g\_frame\_layer = bitmap\_layer\_create(GRect(7, 56, 129, 60));bitmap\_layer\_set\_bitmap(g\_frame\_layer, g\_frame\_bitmap);layer\_add\_child(window\_get\_root\_layer(window), bitmap\_layer\_get\_layer(g\_frame\_layer)); ``` Once again, remember to add calls to `_destroy()` for each element to free memory in `window_unload()`: ``` void window\_unload(Window \*window){//We will safely destroy the Window's elements here!text\_layer\_destroy(g\_text\_layer);gbitmap\_destroy(g\_frame\_bitmap);bitmap\_layer\_destroy(g\_frame\_layer);} ``` With these steps completed, a re-compile of your project should yield something similar to that shown below: ![frameadded](https://developer.repebble.com/assets/images/blog/tut_2_frame_added.png "frame added") The [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is now surrounded by a crisp frame border. It's not amazing, but it's the start on a journey to a great watchface! ### Conclusion There you have it: turning your simple watchapp into a basic watchface. In summary: 1. Modify the App Kind to be a watchface instead of a watchapp. 2. Add a subscription to the [`TickTimerService`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TickTimerService.md "TickTimerService") to get the current time. 3. Modify the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") layout to better suit the task of telling the time. 4. Add an image resource to the project 5. Display that image using a combination of [`GBitmap`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GBitmap "GBitmap") and [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer"). From there you can add more images, change the text font and size and more to further improve the look of the watchface. In future posts you will learn how to use [`Timer`](https://developer.repebble.com/docs/c/Foundation/Timer.md "Timer")s and [`Animation`](https://developer.repebble.com/docs/c/User_Interface/Animation.md "Animation")s to make it even better! ### Source Code Just like last time, the full source code file can be found [here](https://gist.github.com/C-D-Lewis/17eb11ab355950595ca2) and directly imported into CloudPebble [as a new project](https://cloudpebble.net/ide/gist/17eb11ab355950595ca2). If your code doesn't compile, have a look at this and see where you may have gone wrong. Send any feedback or questions to [@PebbleDev](http://twitter.com/PebbleDev) or [@Chris\_DL](http://twitter.com/Chris_DL). * * * --- # Android for Pebble Beta Channel Source: https://developer.repebble.com/blog/2014/06/12/Android-Beta-Channel/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 12 June 2014 - [Ryan Case](https://developer.repebble.com/blog/authors/ryan/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/06/12/Android-Beta-Channel.md#comments) _And what rough beast, its hour come round at last, slouches towards the Play Store to be born?_ Sign up for the Pebble Android [Beta Channel](https://groups.google.com/group/pebble-android-beta) to find out! New Pebble Android releases will be made available to beta testers on this channel. Once you have joined the Google Group, you will need to [opt-in](https://play.google.com/apps/testing/com.getpebble.android). Once signed up and opted-in, you will receive beta versions of the Pebble Android APK through the Play Store. There may be a delay before Google Play transitions you from the release channel to the beta channel. * * * --- # Pebble SDK Tutorial (Part 1) Source: https://developer.repebble.com/blog/2014/06/10/Pebble-SDK-Tutorial-1/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 10 June 2014 - Chris Lewis ## Your First Watchapp ### Introduction This is the first in a series of articles that will hopefully help newcomers and seasoned programmers alike begin to flex their creative muscles and make their own Pebble watchapps. If you prefer a more visual approach, Thomas has made videos you can watch [here](https://www.youtube.com/channel/UCFnawAsyEiux7oPWvGPJCJQ) to help you get started. Firstly, make sure you are familiar with the following basic C language concepts. Pebble has [Developer Guides](https://developer.repebble.com/guides.md) that may help: - Variables and variable scope (local or global?) - Functions (definitions and calls) - Structures - Pointers - Program flow (if, else etc.) - Preprocessor statements (`#define`, `#include` etc.) Up to speed? Good! Let's create our first watchapp. ### Development Environment The easiest way to get started is to use [CloudPebble](https://cloudpebble.repebble.com/) , a free online IDE for Pebble. This approach requires no installations to start making watchapps. Advanced users can install the SDK on Linux, OS X or Windows (via VM such as [VirtualBox](https://www.virtualbox.org/)) for a more hands-on approach. Instructions for installing the native SDK can be found on the [Pebble Developer site](https://developer.repebble.com/sdk/install/linux/) . For this tutorial, we will be using CloudPebble. ### First Steps To get started, log into [CloudPebble](https://cloudpebble.repebble.com/) and choose 'Create Project'. 1. Enter a suitable name for the project such as 'My First App'. 2. Set the project type to 'Pebble C SDK'. 3. Set the template as 'Empty project'. 4. Confirm with 'Create'. To start entering code, choose 'New C file' on the left hand pane. C language source files typically have the extension '.c'. A suggested standard name is `main.c`, although this can be anything you like. ### Setting Up the Basics The power behind the C language comes from its ability to be adapted for many different devices and platforms, such as the Pebble watch, through the use of SDKs such as this one. Therefore, to be able to use all the features the Pebble SDK has to offer us, we must include it at the start of the file by using the `#include` preprocessor statement (meaning it is processed before the rest of the code): ``` #include \ ``` All C applications begin with a call to a function called `main()` by the host operating system, also known as the ‘entry point’ of the program. This must contain [`app_event_loop()`](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop "app\_event\_loop"), and by convention also contains two other functions to manage the start and end of the watchapp's life: - `init()` - Used to create (or initialize) our app. - [`app_event_loop()`](https://developer.repebble.com/docs/c/Foundation/App.md#app_event_loop "app\_event\_loop") - Handles all events that happen from the start of the app to the end of its life. - `deinit()` - Used to clean up after ourselves and make sure any memory we use is free for apps that are run after us. Following these rules, our first function, `main()` looks like this: ``` int main(void){init();app\_event\_loop();deinit();} ``` All functions we call must be defined before they are used so that they are known to the compiler when the first call is encountered. To this end we will define our `init()` and `deinit()` functions before they are called in `main()` by placing them after the `#include` preprocessor statement and before the definition of `main()` itself. The resulting code file now looks like this: ``` #include \void init(){//Create app elements here}void deinit(){//Destroy app elements here}int main(void){init();app\_event\_loop();deinit();} ``` ### Using the SDK With the boilerplate app structure done, let's begin using the Pebble C SDK to create our first watchapp. The first element we will use is the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). We will declare our first instance of a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") outside the scope of any function in what is known as a 'global variable', before it is first used. The reason for this is for us to be able to use this reference from any location within the program. By convention, a global variable's name is prefixed with `g_` to make it easily identifiable as such. An ideal place for these declarations is below the preprocessor statements in the source file. Declare a pointer to a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") structure to be created later like so: ``` Window \*g\_window; ``` At the moment, this pointer does not point anywhere and is unusable. The next step is to use the [`window_create()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_create "window\_create") function to construct a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") element for the pointer to point to. Once this is done, we also register two references to the `window_load()` and `window_unload()` functions (known as handlers) which will manage the creation and destruction of the elements within that window. This is shown below: ``` void init(){//Create the app elements here!g\_window = window\_create();window\_set\_window\_handlers(g\_window, (WindowHandlers) {.load = window\_load,.unload = window\_unload,});} ``` The next logical step is to define what we mean by `window_load()` and `window_unload()`. In a similar fashion to `init()` and `deinit()`, these must be defined before they are first used; above `init()`, but below the definition of `g_window` at the top of the file. Think of them as the `init()` and `deinit()` equivalent functions for the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "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!} ``` This modular approach to app creation allows a developer to create apps with all relevant sub-elements managed within the life of that particular [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). This process is shown visually in the diagram below: ![Lifecycle](https://developer.repebble.com/assets/images/blog/tut_1_lifecycle.png "Lifecycle") As a responsible app developer, it is a good practice to free up any memory we use to the system when our app is closed. Pebble SDK elements use functions suffixed with `_destroy` for this purpose, which can be done for our [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") element in `deinit()`: ``` void deinit(){//We will safely destroy the Window's elements here!window\_destroy(g\_window);} ``` The final step in this process is to make the app actually appear when it is chosen from the Pebble menu. To do this we push our window to the top of the window stack using [`window_stack_push()`](https://developer.repebble.com/docs/c/User_Interface/Window_Stack.md#window_stack_push "window\_stack\_push"), placing it in the foreground in front of the user. This is done after the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") is created, and so we will place this call at the end of `init()`: ``` window\_stack\_push(g\_window, true); ``` The second argument denotes whether we want the push to be animated. This looks cool, so we use `true`! The app will now launch, but will be completely blank. Let's rectify that. ### Displaying Content So far we have created and pushed an empty [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") element. So far so good. Now let's add our first sub-element to make it show some text. Introducing the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer")! This is an element used to show any standard string of characters in a pre-defined area, called a 'frame'. As with any element in the SDK, we begin with a global pointer to a structure-to-be of that type: ``` TextLayer \*g\_text\_layer; ``` The rest of the process of using the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") is very similar to that of the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window"). This is by design, and means it is easy to tell which functions to use as they are named in a 'plain English' style. Creating the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") will be done within the `window_load()` function, as it will be shown within the parent [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") (here called `g_window`). The functions we call to perform this setup are almost self-explanatory, but I will go through them after the following segment. Can you spot the pattern in the SDK function names? ``` g\_text\_layer = text\_layer\_create(GRect(0, 0, 144, 168));text\_layer\_set\_background\_color(g\_text\_layer, GColorClear);text\_layer\_set\_text\_color(g\_text\_layer, GColorBlack);layer\_add\_child(window\_get\_root\_layer(window), text\_layer\_get\_layer(g\_text\_layer)); ``` Now the explanation: 1. [`text_layer_create()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_create "text\_layer\_create") - This creates the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") and sets its frame to that in the [`GRect`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GRect "GRect") supplied as its only argument to x = 0, y = 0, width = 144 pixels and height = 168 pixels (this is the size of the entire screen). Just like [`window_create()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_create "window\_create"), this function returns a pointer to the newly created [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") 2. [`text_layer_set_background_color()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_background_color "text\_layer\_set\_background\_color") - This also does what it says: sets the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer")'s background color to [`GColorClear`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorClear "GColorClear"), which is transparent. 3. [`text_layer_set_text_color()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text_color "text\_layer\_set\_text\_color") - Similar to the last function, but sets the text color to [`GColorBlack`](https://developer.repebble.com/docs/c/Graphics/Graphics_Types/Color_Definitions.md#GColorBlack "GColorBlack"). 4. [`layer_add_child()`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#layer_add_child "layer\_add\_child") - This is used to add the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer")'s "root" [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") (which is the part drawn to the screen) as a 'child' of the [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")'s root layer. Simply put, all child [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer")s are drawn in front of their 'parents' and allows us to control layering of [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer")s and in which order they are drawn. As should always be the case, we must add the required destruction function calls to free up the memory we used in creating the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). This is done in the parent [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window")'s `window_unload()` function, which now looks like this: ``` void window\_unload(Window \*window){//We will safely destroy the Window's elements here!text\_layer\_destroy(g\_text\_layer);} ``` Now for what we have been working towards all this time - making the app show any text we want! After creating the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"), add a call to [`text_layer_set_text()`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md#text_layer_set_text "text\_layer\_set\_text") to set the text it should display, such as the example below: ``` text\_layer\_set\_text(g\_text\_layer, "Anything you want, as long as it is in quotes!"); ``` Now you should be ready to see the fruits of your labor on your wrist! To do this we must compile our C source code into a `.pbw` package file that the Pebble app will install for us. ### Compilation and Installation To do this, make sure you save your C file. Then go to the 'Compilation' screen in the left pane and click 'Run build'. After the compiler returns 'Succeeded', you can use either of the following options to install the app on your Pebble: - Ensure you have enabled the [Pebble Developer Connection](https://developer.repebble.com/guides/tools-and-resources/developer-connection.md) and enter the IP address shown into the 'Phone IP' box. Click 'Install and Run' to install your app. - Use the 'Get PBW' button in your phone's browser to install via the Pebble app. If your code does not compile, compare it to the [sample code](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) to see where you might have made an error. Once this is successful, you should see something similar to the screenshot below: ![Screenshot](https://developer.repebble.com/assets/images/blog/tut_1_preview.png "Screenshot") ### Conclusions There you have it, a _very_ simple watchapp to show some text of your choosing. This was done by: 1. Setting up boilerplate app structure. 2. Creating a [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") with a child layer, in this case the [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer"). 3. Creating a [`TextLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/TextLayer.md "TextLayer") to show text on the screen. By adding more types of layers such as [`BitmapLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/BitmapLayer.md "BitmapLayer") and `InverterLayer` we create much more sophisticated apps. By extension with [`AppMessage`](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage")s and [`Clicks`](https://developer.repebble.com/docs/c/User_Interface/Clicks.md "Clicks") we can begin to respond to user data and input. All this and more to come in future instalments! ### Source Code The full source code file we have built up over the course of this article can be found [here](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) and directly imported into CloudPebble [as a new project](https://cloudpebble.net/ide/gist/a911f0b053260f209390). If your code doesn't compile, have a look at this and see where you may have gone wrong. Best of luck, and let me know what you come up with! Send any feedback or questions to [@PebbleDev](http://twitter.com/PebbleDev) or [@Chris\_DL](http://twitter.com/Chris_DL). * * * --- # FreeRTOS™ Code Revisions from Pebble Source: https://developer.repebble.com/blog/2014/05/23/FreeRTOS-Modifications-From-Pebble/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 23 May 2014 - [Brad Murray](https://developer.repebble.com/blog/authors/brad/) - [Down the Rabbit Hole](https://developer.repebble.com/blog/tags/down-the-rabbit-hole/) - [Comments](https://developer.repebble.com/blog/2014/05/23/FreeRTOS-Modifications-From-Pebble.md#comments) Today Pebble is releasing its recent modifications to the FreeRTOS project. Pebble has made a few minor changes to FreeRTOS to enable its new sandboxed application environment for PebbleOS 2.0 as well as to make Pebble easier to monitor and debug. The changes are available [as a tarball](http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/dev-portal/FreeRTOS-8.0.0-Pebble.tar.gz) . Read on to learn more about the changes and why they were made. The new PebbleOS 2.0 application sandbox environment allows running third party native code (Pebble Apps) in a safe and secure manner. In the sandbox, any application errors should not negatively impact the host operating system. Because of the hardware restrictions of a Pebble watch, the implementation is achieved in a basic manner. Pebble Apps are run in their own FreeRTOS task that is unprivileged. This means that the instructions the app is authorized to run are restricted. For example, it’s not allowed to change the `CONTROL` register (which changes whether the app is privileged or not) or change interrupt settings. Furthermore, Pebble restricts the memory the app can access to a small fixed region. This is done using the MPU (Memory Protection Unit) hardware available in the Pebble microcontroller. Accesses outside of this region will cause the application to be stopped. This way Pebble can make sure the application is only interacting with the kernel in ways that will not interfere with other features and functions. The FreeRTOS implementation includes a port that uses the MPU (ARM\_CM3\_MPU) which is incompatible with the project goals. This port appears meant for safety critical environments where tasks shouldn’t be allowed to accidentally interact with each other. However, there doesn’t seem to be any protection from a malicious task. For example, raising a task's privilege level is as easy as triggering a software interrupt, which unprivileged tasks are free to use. “MPU wrapper” functions are provided to allow any task to call a FreeRTOS API function from any privilege level and operate as a privileged function, ignoring any MPU settings. The sandbox is intended to restrict how the application FreeRTOS task is allowed to interact with the kernel, so modifications were necessary. To solve this, Pebble has created its own port named ARM\_CM3\_PEBBLE. This port is based on the ARM\_CM3\_MPU port, but is modified to use the MPU in a different way. No wrappers are provided (you need to be privileged to directly call FreeRTOS functions) and the `portSVC_RAISE_PRIVILEGE` software interrupt is removed. To permit the app to interact with the system in a safe manner, Pebble added a new software interrupt, called `portSVC_SYSCALL`. Unprivileged code can use it to jump into the operating system in a privileged state but only using a system- provided jump-table. The jump-table contains the address to landing functions (we refer to them as syscalls) that are individually responsible for checking that the operation is permitted and the parameters are safe and valid. Pebble has also made some minor changes to how tasks are created and destroyed. In order for the application to keep working inside its sandbox, it needs access to certain resources like its own stack memory. FreeRTOS allows a programmer to specify a buffer to be used as the stack, so a buffer that's allocated inside the app sandbox memory region is used. However, FreeRTOS has a bug where it tries to deallocate the stack memory when a task is destroyed, regardless of where that memory came from. Pebble changed this code to only free the stack when it was not provided by the system. Pebble also added the ability for the system to designate its own buffer for the `_reent` struct. This struct is a feature of the c library we use - newlib - that contains buffers that are used by various libc functions. For example, `localtime` returns a pointer to a `struct tm` structure. This struct is actually stored in the `_reent` struct. FreeRTOS has `_reent` support so which `_reent` struct is currently being used is swapped around per task, so each task has their own `_reent` struct to play with and you don’t have issues if two threads call `localtime` at the same time. To ensure the `_reent` struct for the application task is available within the sandboxed memory region, Pebble pre- allocated it and passed it through to `xTaskCreate`. Finally, Pebble has added some functionality to inspect the saved registers of tasks that aren’t currently running. This allows Pebble to collect additional diagnostics if the watch exhibits any errors and to provide basic crash reporting for apps. For example, App developers will get a dump of the app’s PC (Program Counter) and LR (Link Register) registers at the time of the crash. Pebble is incredibly thankful for all the work that the FreeRTOS community has put into the code. It has been a huge asset when building PebbleOS. We'll be releasing additional updates as we continue to modify FreeRTOS in the future. If you have any questions, don’t hesitate to [contact us](https://developer.repebble.com/contact.md). * * * --- # Pebble Updates, MakerFaire & MakingAwesomeHappen Source: https://developer.repebble.com/blog/2014/05/15/Pebble-2.1-Released/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 15 May 2014 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/05/15/Pebble-2.1-Released.md#comments) Last week, we released both the 2.1 Pebble firmware, as well as the 2.1 Pebble SDK. Please make sure you have the latest version of the SDK (2.1.1) -- the latest update is available from the [developer site](https://developer.repebble.com/sdk.md). Some of the highlights of the new 2.1.1 firmware/SDK are: - Pebble dynamic memory allocation has been improved and will now detect when you try to [`free()`](https://developer.repebble.com/docs/c/Standard_C/Memory.md#free "free") memory twice. With Pebble 2.1, your application will be killed and a message displayed in the console so you can detect and fix this problem, instead of potentially causing a memory corruption issue. - You will need to update your Pebble to run apps built with the 2.1 SDK. Applications compiled with the 2.1 SDK will not appear in the menu and will not run on Pebble firmware 2.0. - Notifications can be cleared via the Notification section in the Settings menu - The `pebble screenshot` tool has been fixed and correctly generates 144x168 screenshots For a more comprehensive listing of changes included in 2.1.1, take a look at the official [release notes](https://developer.repebble.com/sdk/changelogs/2.1.1.md) ![](https://developer.repebble.com/assets/images/blog/pebble-211.png) ## CloudPebble Updated with New Look & Features CloudPebble saw an update to its look last week and is now easier to use than ever! Some of the functional improvements to CloudPebble include: - CloudPebble now lets you create applications using pure JavaScript with the [SimplyJS](http://simplyjs.io) library! - You can now view the API documentation right in CloudPebble! Alt-click on any API function in your code for a pop-up explaining it. You can dismiss it by clicking or hitting esc. You can also view the pop-up for the name your editor cursor is in by pressing cmd-ctrl-shift-/ (Mac) or ctrl-alt-shift-/ (Windows). Short summaries now also appear at the bottom of the autocomplete popup. - CloudPebble can now also import and open code created using GitHub’s handy [Gists](http://gist.github.com) - iOS users can now more easily install apps on their phones by selecting it from a list! This requires iOS app 2.1.1. If you have 2.1.1 and your phone doesn't appear in the list, try killing and restarting the Pebble app - A longstanding issue preventing iOS users from installing apps larger than 64k has been resolved in iOS app 2.1.1 ![](https://developer.repebble.com/assets/images/blog/cloudpebble-update.png) ## Get a Free Ticket to Show off Your Pebble Projects at MakerFaire In the Bay Area and want to demo a cool Pebble hardware hack you've built? Join us this weekend (May 17th-18th) at Maker Faire Bay Area! Tell us about your project with [this form](http://eepurl.com/ULifX) and your Maker Faire ticket will be paid by Team Pebble if we select your hack for demo. NOTE: Ticket winners are responsible for their own transportation to Maker Faire at the San Mateo Event Center. ## Pebble Devs #makeawesomehappen We’d like to take the time to also share some great developer contributions to the PebbleDev community: - [Chris Lewis](https://twitter.com/Chris_DL) put together a terrific [blog post](http://ninedof.wordpress.com/2014/05/12/linking-pebble-and-spark-core/) on how to connect and integrate Pebble with the Spark core. - The [HelloSwimIO](https://twitter.com/helloswimio) team wrote an informative [blog post](http://helloswimio.tumblr.com/post/82087337072/setting-up-sublime-text-3-for-pebble-development) on how to setup SublimeText for development of Pebble apps. - [James Brooks](https://twitter.com/jbrooksuk/) has also developed a useful Sublime plugin for Pebble that is available on [GitHub](https://github.com/jbrooksuk/Sublime-Pebble). Pebblers like [Tyler Hoffman](http://tyhoffman.com) have already started to contribute to this open source project! * * * --- # Learning to Program Your Pebble Source: https://developer.repebble.com/blog/2014/04/30/Learn-to-Program-My-Pebble/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 30 April 2014 - [Tom Maremaa](https://developer.repebble.com/blog/authors/tom/) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2014/04/30/Learn-to-Program-My-Pebble.md#comments) Where can you find a good tutorial on how to program your Pebble? That question pops up frequently among readers on Pebble Developer Forums. Fortunately, a Pebble developer has provided the answer. In the Pebble community, Chris Lewis, a UK-based developer, has produced, beginning with Pebble SDK 1.x and recently with SDK 2.0, that essential tutorial for newbies coming to the platform on his blog: [—try { work(); } finally { code(); }](http://ninedof.wordpress.com/pebble-sdk-tutorial/) . You may want to check it out! ## The Essential Pebble Tutorial To begin with, Chris divides the Pebble tutorial into two parts, the first of which is aimed at beginners who are new to the C programming language. In the second part, you learn progressively about the various components of the Pebble SDK, from building your first watchapp, telling time, handling fonts and images, dealing with animations, timers, buttons, vibrations and menu layers, on a learning path all the way up to how to integrate Android apps. By that time, you'll have a good handle on the basics of programming your Pebble. The source code for each type of app is freely available, as is the source for most of Chris's Pebble watchfaces and apps on [GitHub](https://github.com/C-D-Lewis), which is a valuable resource for building your own library of Pebble apps. ## About Chris and His Pebble Apps Chris Lewis is a UK-based app developer, who started learning to code in C at University while studying Electronic Engineering. Since then he has programmed in Java for Android in his free time, before coming back to C for Pebble SDK. "The combination of the two has resulted in Pebble apps that leverage Android features to help streamline everyday life," he says. ![Chris Lewis](https://developer.repebble.com/assets/images/blog/chris-qna.png) Chris has developed and published several cool Pebble apps, notably - [Watch Trigger+](https://play.google.com/store/apps/details?id=com.wordpress.ninedof.watchtrigger) , which lets you handle remote triggering of video capture, as well as photo capture, on your Pebble. ![Watch Trigger+](https://developer.repebble.com/assets/images/blog/watchtrigger.png) - [Wristponder](https://play.google.com/store/apps/details?id=com.wordpress.ninedof.wristponder) enables you to send customized pre-set SMS messages from your Pebble when you need to reply, but when you find that it isn't socially acceptable to use your mobile device. ![Wristponder](https://developer.repebble.com/assets/images/blog/wristponder.png) - [Watch App Selector](https://play.google.com/store/apps/details?id=com.wordpress.ninedof.watchappselector) lets you pull together all of Chris’s watchapps and watchfaces with one Android app for easy updating. ![Watch App Selector](https://developer.repebble.com/assets/images/blog/app-selector.png) ## Q and A With Chris We caught up with Chris recently and engaged in an online discussion about how best to learn Pebble programming. Q: Do you recommend that non-programmers engage in learning how to program a Pebble app, perhaps just using CloudPebble? - A: I do recommend non-programmers engage, as it allows a Pebble user to unleash their own ideas and experiment with the platform. This is especially true if they like the idea of beginning to program, as once you have a basic working knowledge of the structure of C, the Pebble SDK is 'plain English' enough to be accessible as a first platform. - An example of this is 'text\_layer\_set\_text()’, which 'does what it says on the tin' and sets the text a TextLayer will show. Once you are familiar with Pebble SDK, you can aim higher to other platforms such as Android, so it's a good starting point. Q: Should they follow your tutorial on creating Pebble apps, or are they going to bite off more than they can chew, as it were? - A: When I wrote the SDK 2.0 Tutorial I decided to try and give it a more progressive element, by starting small and working up to more advanced topics. Once a reader has variables, functions, control flow (if, else etc.) under their belt they should be able to progress along with it. Of course, I wrote it as an experienced user, so I hope I struck the right balance. Q: How do you see working with CloudPebble? Is it a kind of IDE that non- programmers or even seasoned programmers should use? What are some of the limitations? Advantages? - A: As a Windows user, an alternative to setting up a Virtual Machine, tool chain and file share has got to be a bonus for non-programmers and seasoned programmers alike. Even more compelling an alternative to a VM now that 'pebble logs' has been added. - When I'm out and don't have access to my laptop but access to another computer I can log in to CloudPebble to try out new ideas. A great example is when I was alerted by users to a big bug in Watch Trigger that had to be fixed fast, and thanks to CloudPebble I could do that as soon as possible without having to go home, preventing user frustration. Q: What parts of the C programming language are essential to know and understand when writing a Pebble app? I’m thinking if you’re coming to Pebble from Objective-C or Java. - A: I’ve seen users who want to make a start in Pebble SDK recommended standard texts on C, which would make them an expert in the language, but they'd also know about ten times the amount they would actually need to make good use of the SDK, which is overkill. - I did quite a bit of Java for Android before re-visiting my limited C for Pebble SDK, and I found that the crucial things you need to know are how to store and use data (variables, structures, pointers and scope) and how to interact with the SDK. - This means an understanding of what a function definition is and the process of calling a function (arguments and return values, etc.). Once you know how to make and call functions, you can use ANY of the SDK functions, which immediately broadens your capabilities. Q: How would you identify the most difficult part of learning to program on Pebble? I’m thinking of the steepest learning curve example? - A: After gaining an initial understanding of C syntax, I think the steepest learning curve for a beginner would be trying to create a 'Grand Design' from scratch with too little experience. When I was learning Java, I found that starting small and then incorporating those small achievements into larger projects helped me improve my understanding of what was possible. - For example, I recently helped a fairly novice user create a MenuLayer based app that used AppMessage to update an Android phone app, and the main difficulty we had was the user's lack of experience of starting small and experimenting with those modules individually beforehand. Think of it as 'building on your own shoulders'. Best to start small with TextLayers and Clicks, for example. Q: Are the [nine tutorials](http://ninedof.wordpress.com/pebble-sdk-tutorial/) in the order that I, as a newbie to the Pebble platform, should follow, as a learning progression? Can I skip any in particular? What’s most critical for me to understand? - A: As I've previously mentioned, the basic syntax of storing and using/moving around data in C is a must. This enables you to use the Pebble SDK without much extra knowledge. - I hope that my SDK 2.0 Tutorial series are written as a natural progression from starting small to more advanced topics, but to get things off to a quicker start and get creating early this basic knowledge is covered in less detail than the first part of the 1.X Tutorial series. - That said, the Part #1 of the 1.X series is still a good introduction to the basic syntax of C, so perhaps I should revisit it as a 2.0 series prequel. Q: Do you have any specific advice for JavaScript developers coming to the Pebble platform? - A: The only practical experience I have with JavaScript was that gained from experimenting with PebbleKit JS, I had none before that. However, it is similar in most respects to Java in syntax but without explicit types. - Wanting to achieve set goals (such as Pebble Tube Status web data fetching) is a sure-fire way of motivating yourself to learn how to do new things with a language, instead of simply reading a book which provides no drive to accomplish things. - So with that in mind, my advice to JS developers would be to familiarize yourself with the differences in doing the same thing in each language. For example, string representations in C are very different than in JS. Being able to manipulate strings in both languages makes passing data between the watch and the phone a lot easier! ### Got a Pebble Programming Story to Tell? Special thanks to Chris for sharing his ideas on learning techniques for Pebble programming. If you have a story to tell about your experiences learning to program your Pebble, please [contact us](https://developer.repebble.com/contact.md) and we'll add you to our Learning to Program Your Pebble feature list. * * * --- # Bundling Javascript for Pebble Apps Source: https://developer.repebble.com/blog/2014/04/22/JS-Bundling/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 22 April 2014 - [Alex Lin](https://developer.repebble.com/blog/authors/alex/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/04/22/JS-Bundling.md#comments) > _Updated Apr 23rd_: "COMING SOON" bug is fixed. > > _Updated May 14th_: We will now make most updates available right away. Your update will be available to both iOS and Android users at the same time if your JS app was previously approved. Newly submitted apps still have to wait for the next version of the iOS app to be made available and will be marked "coming soon". We get a lot of questions about apps that use JavaScript and their availability in the store. We are improving the Pebble appstore documentation and the Developer Dashboard to address these questions, but until those changes are live, we thought we would summarize the key points in a blog post. First, a little reminder: Pebble applications that use PebbleKit JavaScript need to have their JavaScript file bundled in the Pebble iOS application before iOS Pebblers can install those apps. Android users are not affected: all Pebble apps are always available right away to Android users. When you submit a new app that uses JavaScript to the Pebble appstore, this app will not be available immediately to iOS users. Instead of the "ADD" button, they will see a "COMING SOON" button. Approximately once every 10 days, we grab the latest release of all published JavaScript apps and include them in a new release of our iOS app. This new release is then sent to Apple. When this new release is approved by Apple, iOS users will automatically upgrade to this version and will then have access to your app. The same process is true for updates but if you have an older version of the app that is published and included in a previous iOS release, we will show this version to iOS users. Users will not see the "COMING SOON" button, but the newest version of your app will not be available until the new iOS app is published and users have updated their iOS apps. **If you need expedite process for a JavaScript update,** you can [contact us](https://developer.repebble.com/contact.md). _Please note:_ We are aware of a bug that caused some apps to appear as "COMING SOON" although an older version was available. We have now fixed this bug. In summary: ![](https://developer.repebble.com/assets/images/blog/FlowchartJS.png) ##### Here’s how the bundling process works: 1. Once we submit our Pebble iOS app to Apple, it takes about a week for Apple to approve it. 2. When we get the signal that the app is approved, we inform developers that we will soon be preparing a new release. Look for this [announcement on the developer forums](https://forums.getpebble.com/discussion/12451/pebble-js-bundling-updates#latest) and our [@pebbledev](https://twitter.com/pebbledev) Twitter account. 3. Updated JavaScript apps are collected and bundled in the iOS app and then submitted in a new version to Apple. The cycle restarts. ##### These are the best practices you should follow: - Submit and publish new versions of your apps as soon as they are ready. They will be available to Android users right away and will be automatically included in the next version of the iOS app. - Do not delete previous releases of your app. If you do so, users will see the "COMING SOON" message instead of having access to the version included in their iOS app. - Do not change your JavaScript file if you do not need to. If the MD5 signature of your JavaScript file does not change, we will detect this and the new version of your app will be available right away to all users. - Expect 7 to 20 days between when you submit a new version of your app and when this version will be available to all users. ##### Future improvements Finally, this is what we are currently working on to improve the experience of developers publishing JavaScript applications: - We've set up a [forum thread](https://forums.getpebble.com/discussion/12451/pebble-js-bundling-updates#latest) to announce when we are going to grab JavaScript from the appstore to submit a new build to Apple, and then when this version is available to iOS users; - We are updating the appstore documentation to explain how the process works; - We will be showing you in the developer dashboard what the current status of your app is and in what version of the iOS application it is or will be in. We are continuously looking to improve the tools and services we provide to the developer community and will keep you updated as we do. As always feel free to [contact us](https://developer.repebble.com/contact.md) with comments, questions and suggestions! * * * --- # API documentation now inside CloudPebble Source: https://developer.repebble.com/blog/2014/04/14/API-documentation-in-CloudPebble/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 14 April 2014 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [CloudPebble](https://developer.repebble.com/blog/tags/cloudpebble/) - [Comments](https://developer.repebble.com/blog/2014/04/14/API-documentation-in-CloudPebble.md#comments) To help make your Pebble development experience even better, CloudPebble can now display the API documentation for any function directly in the editor view. First up, a brief summary of each function now appears at the bottom of the autocomplete popup, explaining the purpose of that function. This could help you remember, for instance, whether you wanted to set a layer's _bounds_ or _frame_. ![autocomplete summary view](https://developer.repebble.com/assets/images/blog/cloudpebble-autocomplete-fn-summary.png) For more detail on any API function, you can hold down the 'alt' key (or 'option' on Macs) and click the function's name, which will show a popover including a description of the function, explanations of its parameters and return values, and any important considerations when using it: ![property_animation_create_layer_frame popover](https://developer.repebble.com/assets/images/blog/cloudpebble-api-popover.png) This popover can be dismissed by clicking elsewhere or hitting the Escape key. In addition to alt-clicking, you can also produce this popover using a keyboard shortcut. If you are using a mac, that shortcut is cmd-ctrl-shift-/ (which is the same as cmd-ctrl-? on US/UK keyboards). Otherwise, use ctrl-alt-shift-/ (or ctrl-alt-?). A popover explaining the function your caret is inside will appear: ![window_long_click_subscribe popover](https://developer.repebble.com/assets/images/blog/cloudpebble-api-popover-keyboard.png) Again, this can be dismissed using the Escape key or by clicking elsewhere. We hope this makes creating apps for Pebble easier and more enjoyable. And if you have suggestions on how we can make your development experience better, let us know [on GitHub](https://github.com/CloudPebble/CloudPebble/issues?state=open)! * * * --- # Space Apps 2014 Source: https://developer.repebble.com/blog/2014/04/12/Space-Apps-Challenge-2014/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 12 April 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2014/04/12/Space-Apps-Challenge-2014.md#comments) Space Apps 2014 is taking place this weekend (April 12th/13th) and we are very happy to be a partner of this event. This year, the challenges [include a Wearable category](https://2014.spaceappschallenge.org/challenge/space-wearables-fashion-designer-astronauts/) but we do not doubt that Pebble will find its way in a lot of the other challenges as well! Pebble is sponsoring four locations this year and we hope to expand this even more in the future. Pebble loaner units are available in: - [Space Apps Reno](https://2014.spaceappschallenge.org/location/reno/) - [Space Apps Toronto](http://spaceappstoronto.com/) - [Space Apps New York](http://spaceappsnyc.com/) - [Space Apps San Francisco](https://2014.spaceappschallenge.org/location/constant-contact/) ## Getting Started for Space Apps 2014 If you are just getting started with Pebble during Space Apps 2014, here are some very useful resources to get your first apps running in minutes: - Our [Introduction to Pebble development video](https://developer.repebble.com/blog/2014/03/27/Introduction-to-Pebble-development.md) will show you how to create a Pebble project using our online IDE [CloudPebble](https://cloudpebble.repebble.com/) and how to deploy your first app to a Pebble - Our [Build a Watchface video](https://developer.repebble.com/blog/2014/03/28/Build-a-Watchface.md) explains the basic concepts of Pebble user interface and how to build a new watchface. If you want to build a watchface that displays the local time on Pluto, it's exactly what you need! - [SimplyJS.io](http://simplyjs.io) is a framework to build Pebble apps completely in JavaScript. It is also included in [CloudPebble](https://cloudpebble.repebble.com/) (just choose the "Simply.js" project type when creating a new project). If your data is available in a web service and you just want to display it on a Pebble screen, this is a great way to do it! - Our [official examples](https://github.com/pebble-examples) and the [not-so-official examples](https://github.com/pebble-hacks) both include lots of cool things: [how to use the GPS and make HTTP requests](https://github.com/pebble-examples/pebblekit-js-weather), and [transfer images from the Internet to Pebble](https://github.com/pebble-examples/pebble-faces), etc. ## Pebble and Space Looking for inspiration? There are already some great Pebble apps for Space-fans: - [Pebble Mars](https://apps.getpebble.com/applications/52cfaf1ef726c833ac000049/) ([source code](https://github.com/witoff/PebbleMars)) displays (almost) realtime pictures from Curiosity, as well as the current time on Mars. ![](https://developer.repebble.com/assets/images/blog/spaceapps-pebblemars.png) - [Pebble Sat](https://apps.getpebble.com/applications/52d396683e2e256b6c00001e/) ([source code](https://github.com/sarfata/pbsat)) automatically calculates and display the time of the next ISS pass over your current location. When the ISS is flying over, it also shows you where to look to spot it! ![](https://developer.repebble.com/assets/images/blog/spaceapps-pebblesat.png) ## Getting Help If you need help, we would like to recommend a few channels to quickly get answers: - The #pebble IRC channel on irc.freenode.net - A lot of experienced Pebblers hang out on this channel and this is a great place to meet other Space Apps Challengers during the week-end. - Visit the [Pebble Developer Forums](https://forums.getpebble.com/categories/developer-discussion). They are also extremely active and already have answers to all the usual questions. - Contact us on [@pebbledev](https://twitter.com/pebbledev) - Most of our developer evangelism team is in Illinois this week-end for [HackIllinois](http://www.hackillinois.org) - Pinging us on twitter is a great way to get our attention. ## Tell us about your projects! We would love to feature all the projects that will be built this year! [Contact us](https://developer.repebble.com/contact.md) and we will include you in a future retrospective of all the Space Apps - Pebble projects. * * * --- # Easier app deployment from CloudPebble Source: https://developer.repebble.com/blog/2014/04/04/Easier-app-deployment-from-Cloudpebble/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 04 April 2014 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [CloudPebble](https://developer.repebble.com/blog/tags/cloudpebble/) - [Comments](https://developer.repebble.com/blog/2014/04/04/Easier-app-deployment-from-Cloudpebble.md#comments) We now have an even easier way for you to deploy apps to your watches! No longer will you have to launch the app, turn on the development server, and type in your IP address. Instead, you now simply need to select your phone from a list. ![CloudPebble phone list](https://developer.repebble.com/assets/images/blog/cloudpebble-phone-list.png) Once you've selected your phone from the list, you can click any of the buttons below it as usual. If your phone's screen is on and the Pebble app is displayed, you don't need to do anything – it's entirely automatic. If your phone is on but the Pebble app is not running, you will see a notification like this, which you can just tap to complete setup. ![CloudPebble banner notice](https://developer.repebble.com/assets/images/blog/cloudpebble-ios-banner-notice.png) If your phone's screen is off, you will receive a notification like this on your lock screen; swipe it to the right to complete the setup process. ![CloudPebble lock screen notice](https://developer.repebble.com/assets/images/blog/cloudpebble-ios-lock-screen-notice.png) Do keep in mind that you must first enable Pebble Developer Mode in the iOS Settings app; if you haven't done this, CloudPebble will ask you to do so and try again. Don't worry about turning on the development server, it'll be switched on for you. This feature requires that you have updated to iOS app 2.1.1. Unfortunately, it does not yet support Android. If we detect that you have no compatible devices we will automatically switch you to the manual IP entry view, where you can proceed as you have in the past. ![Legacy IP entry](https://developer.repebble.com/assets/images/blog/cloudpebble-legacy-ip-entry.png) If you _do_ have supported devices but want to install on some other device for any reason, you can also enter the IP manually by clicking the "Enter phone IP manually." link under the dropdown. We hope this makes it easier for you to get apps on to your watch! * * * --- # Finalists Of The Pebble App Challenge Source: https://developer.repebble.com/blog/2014/03/28/Finalists-of-the-Pebble-App-Challenge/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 28 March 2014 - - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/03/28/Finalists-of-the-Pebble-App-Challenge.md#comments) **UPDATE:** The next round of Pebble App Challenge voting—narrowing down our 16 finalists—has been postponed for now. We will update the voting schedule after [a recent issue with the iOS Pebble app](https://forums.getpebble.com/discussion/12296/issue-pebble-ios-app-v2-1-updated-31-mar-crashing/p1) has been resolved. The public has spoken! The Top16 finalists for the Pebble App Challenge have been selected! Here is the list of apps that will compete next week in the Pebble appstore for the title of best app in the 2014 Pebble App Challenge! - [MagikSlide](http://pebble.challengepost.com/submissions/22048-magikslide) - [Smartwatch+](http://pebble.challengepost.com/submissions/22037-smartwatch) - [Pebble Movies](http://pebble.challengepost.com/submissions/22028-pebble-movies) - [Pebblin' - A Musical Smartwatch Experience](http://pebble.challengepost.com/submissions/21844-pebblin-a-musical-smartwatch-experience) - [Pebble Communicator](http://pebble.challengepost.com/submissions/21872-pebble-communicator) - [Rebble](http://pebble.challengepost.com/submissions/21824-rebble) - [PebblePool](http://pebble.challengepost.com/submissions/22043-pebblepool) - [PebbleBan](http://pebble.challengepost.com/submissions/21938-pebbleban) - [Glance](http://pebble.challengepost.com/submissions/21671-glance) - [AutoPebble - Automate your life from your wrist](http://pebble.challengepost.com/submissions/21617-autopebble-automate-your-life-from-your-wrist) - [Futura Weather](http://pebble.challengepost.com/submissions/21590-futura-weather) - [Pebble Bike](http://pebble.challengepost.com/submissions/21851-pebble-bike) - [Music Boss](http://pebble.challengepost.com/submissions/21537-music-boss) - [Mr.Handy](http://pebble.challengepost.com/submissions/22042-mr-handy) - [Forecast Weather](http://pebble.challengepost.com/submissions/21813-forecast-weather) - [Morpheuz Sleep Monitor](http://pebble.challengepost.com/submissions/21750-morpheuz-sleep-monitor) Congratulations to our finalists! They will all receive a Pebble Steel. We had a large number of entries in the Challenge and we would like to thank all of our developers who submitted apps. If you were not selected this time, rest assured that we will have more opportunities to showcase your apps. We hope the visibility brought by the Challenge helped you get more downloads! * * * --- # Build a Watchface Source: https://developer.repebble.com/blog/2014/03/28/Build-a-Watchface/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 28 March 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/03/28/Build-a-Watchface.md#comments) In this video we walk you through your first Pebble Watchface. Learn how to: - Build a Pebble app that behaves like a watchface - Display the time and the date - Arrange text fields on the screen - Use the various system fonts - Invert colors in one part of the screen This is a follow up to our first video: "Introduction to Pebble development" - We recommend you start [with that one](https://developer.repebble.com/blog/2014/03/27/Introduction-to-Pebble-development.md) if you are new to Pebble. This series of videos is intended for anyone with previous development experience. You certainly do not need to be a C expert but if you have never programmed, we recommend starting with [the Watchface Generator](http://www.watchface-generator.de/) or [Pebble Canvas](https://play.google.com/store/apps/details?id=com.pennas.pebblecanvas) (Android app). The blog post mentioned in the video with all the Pebble system fonts [is available here](https://developer.repebble.com/blog/2013/07/24/Using-Pebble-System-Fonts.md). This is the complete source code built in this video: Cheers! * * * --- # Introduction to Pebble development Source: https://developer.repebble.com/blog/2014/03/27/Introduction-to-Pebble-development/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 27 March 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/03/27/Introduction-to-Pebble-development.md#comments) If you are new to Pebble development you are probably wondering: What does Pebble source code look like? How do I compile an app? How do I install it on my Pebble? These are all excellent questions and the answer takes less than 5 minutes to demonstrate! And we hope you will be following along on your computer! In this tutorial we use [CloudPebble.net](https://cloudpebble.repebble.com/). There is nothing to install on your computer, you can do this in any web browser. The CloudPebble template used in the video is "Hello World", and this is the complete source code: We hope you will enjoy this video. Please use the comments here or on YouTube to tell us what you think of this format and what other videos you would like to see! Cheers! * * * --- # Use AppCode to Develop for the Pebble Smartwatch Source: https://developer.repebble.com/blog/2014/03/27/Appcode-Webinar-With-Heiko/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 27 March 2014 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2014/03/27/Appcode-Webinar-With-Heiko.md#comments) We'd like to share with you a great opportunity to learn more about Pebble development through a webinar that is taking place on Monday, March 31st from 14:00 – 15:00 GMT (10:00 AM – 11:00 AM EDT). The webinar is entitled "Use AppCode to Develop for the Pebble Smartwatch" and is being presented by [Heiko Behrens](http://twitter.com/hbehrens), one of the developers of [PebbleCam](http://pebblecam.com). The webinar will cover how AppCode can help you integrate your existing mobile apps with Pebble, as well as introduce you to the PebbleKit JS library for standalone watchapps. Make sure to [register for the webinar](http://info.jetbrains.com/AppCode-Webinar-Registration-March2014.html), space is limited! * * * --- # Vote now for your favorite apps in the Pebble App Challenge Source: https://developer.repebble.com/blog/2014/03/24/Pebble-App-Challenge-vote-now/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 24 March 2014 - - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2014/03/24/Pebble-App-Challenge-vote-now.md#comments) ![](https://developer.repebble.com/assets/images/blog/pebble-app-challenge-vote-now.jpg) Dear Pebble Developers, Submissions are now closed, and you can vote for your favorite apps in the Pebble App Challenge. Visit the [challenge page](http://pebble.challengepost.com/submissions) to review the submissions and cast your vote. Now is a great time to encourage your friends, colleagues and peers to support your work and vote. The voting period this week is open to everyone, including non-Pebble users. Voting will be open until Thursday 5 pm PDT. At the end of the week, the Top 16 apps will be selected to compete next week in the Pebble appstore for the grand prize. May the best app win! Team Pebble ## Appstore Launch Pebble Steel Winners We're pleased to announce the winners of the Pebble Appstore launch prizes for the four categories described in [our January blog post](https://developer.repebble.com/blog/2014/01/06/Pebble-appstore.md). ### Prize #1: All Watch Apps & Faces We randomly selected an app that was available at the time of the Pebble appstore launch on February 4th. This prize goes to [BITCOIN WATCHER](https://apps.getpebble.com/applications/52db0b8d889f5d8253000313), which was developed and submitted by Gh0st Development. [BITCOIN WATCHER](https://apps.getpebble.com/applications/52db0b8d889f5d8253000313) helps you keep track of the current Bitcoin price across several large trades. ### Prize #2: Apps using PebbleKit JS We randomly selected an app that made use of the new PebbleKit JS API's. This prize goes to [Rapid T & W](https://apps.getpebble.com/applications/52b57d30b6d9ae1a76000016), which was developed and submitted by Luca Infante. Rapid T & W displays the time, date and battery with a quick flick of the wrist in the Y axis. With a quick movement on the Z axis, get the weather information. With a quick movement on the X axis, see the stats of accelerometer tap event. ### Prize #3: Apps Ported from 1.x to 2.0 We randomly selected an app that was ported from an app created using the Pebble 1.0 SDK to the new Pebble 2.0. This prize goes to [Interval Trainer](https://apps.getpebble.com/applications/52dccaa419ed61877c000074), which was developed and submitted by SetPebble. Interval Trainer tracks elapsed time and interval times. A vibration alerts you when an interval changes, and the numbers are big for easy reading while exercising. ### Prize #4: Best Bug Report on the Developer Forums Our prize for the [best bug report on the developer forums](https://forums.getpebble.com/discussion/10343/ios-bugreport-beta6-try-turning-it-off-and-on-again) was chosen by one of our iOS engineers for its completeness, which helped in tracking down a major 2.0 bug. This bug report is a fantastic example of what we expect from developers that are reporting bugs, and we highly recommend all developers review this example to help improve the time it takes to resolve any bugs or issues. Martin Norland, the winner, found this bug while developing his watchface [Timely](https://apps.getpebble.com/applications/52978a53bbd0862701000002), which is also available on the Pebble appstore. All the winners have been contacted and their Pebble Steel are on the way! * * * --- # Pebble goes to PennApps Source: https://developer.repebble.com/blog/2014/03/20/pebble-goes-to-pennapps/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 20 March 2014 - [Alexey Komissarouk](https://developer.repebble.com/blog/authors/alexey/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2014/03/20/pebble-goes-to-pennapps.md#comments) This February, three Pebble developer evangelists headed out to Philadelphia for PennApps 2014. It wasn't easy. First, our flights got cancelled due to Stormageddon 2014. Then, it was Valentine's Day. ![](https://developer.repebble.com/assets/images/blog/pennapps_2014_alien.jpg) The point is, we made it - and PennApps this year was worth it. Easily. #### Some quick stats: - **Hackers** : Over 1,000 - **Hackers with Pebbles** : Now over 150 - **Hacks** : [Over 200](http://pennapps2014s.challengepost.com/submissions) - **Pebble Hacks** : [Over 20](http://pennapps2014s.challengepost.com/submissions/search?utf8=%E2%9C%93&terms=pebble&sort=alpha) - **Hackathon Hours** : 40 - **Hours Slept** : N/A. ![](https://developer.repebble.com/assets/images/blog/pennapps_2014_demohall.jpg) A number of polished Pebble apps came out of PennApps, a feat made particularly impressive given that only one of the 30+ teams had ever used the brand new Pebble SDK 2.0 before. When the the time came to pick the winner of our Best Wearable Hack prize, we spent over fifteen minutes deliberating (for comparison, it took a lot longer to judge the Pebble prize than the Grand Prize). ## Best Wearable Hack: Sober Up Built by Evan, [@matthalefi](http://twitter.com/matthalefi), [@jkiske](http://twitter.com/jkiske) and [@kevindcon](http://twitter.com/kevindcon) [![](http://pblweb.com/badge/530db564ae03dcd9cf00024d/white/medium/)](https://apps.getpebble.com/applications/530db564ae03dcd9cf00024d) [Sober Up](https://apps.getpebble.com/applications/530db564ae03dcd9cf00024d/) won the Best Wearable Hack by helping Pebble wearers keep track of their beverage consumption. Given your weight and gender, SoberUp estimates your BAC to help make drivers make responsible decisions when and whether to drive home. ![](https://developer.repebble.com/assets/images/blog/soberup.png) ## Pebble Selection: our Top 10. SoberUp was an impressive (and useful) app, but it was far from the only app that deserves recognition. Here were some of our other favorite Pebble apps from PennApps: #### [Gestice](http://gestice.org/) Built by [@MarcoChiangTW](http://twitter.com/MarcoChiangTW), [@stanzheng](http://twitter.com/stanzheng), Shane and Victor. Unlock your phone with a shake of your wrist with Gestice, an app that correlates the accelerometer of your Pebble and your Android phone as a sort of secret handshake. As a bonus, depending on the direction in which you shake your phone, a different app comes up when you open. #### [Pebblin](https://apps.getpebble.com/applications/532b66bcda514ee90000001d/), the Pebble violin. Built by [@rbtying](http://twitter.com/rbtying), [@aditya\_majumdar](http://twitter.com/aditya_majumdar), [@a2linx](http://twitter.com/a2linx) and Kevin. Pebblin lets users simulate playing a violin, using their phone as the body and the Pebble as the bow. [![Pebblin on the Appstore](http://pblweb.com/badge/532b66bcda514ee90000001d/white/medium/)](https://apps.getpebble.com/applications/532b66bcda514ee90000001d)[![Android app on Google Play](https://developer.android.com/images/brand/en_app_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=com.lynbrook.pebblin) #### [Pebble Messaging Service](http://pennapps2014s.challengepost.com/submissions/20853-pebble-messaging-service) Built by [@DavidPatuwo](http://twitter.com/DavidPatuwo), [@danielgur](http://twitter.com/danielgur) and [@camdenfullmer](http://twitter.com/camdenfullmer) If you've got a Jailbroken iPhone, Pebble Messaging Service lets you see incoming SMS and respond with canned responses right from your Pebble. #### [Rebble](http://pennapps2014s.challengepost.com/submissions/20939-rebble), the first Pebble Reddit Client Built by Joseph, Gary, [@lDavixl](http://twitter.com/lDavixl) and Chaoren ![](https://developer.repebble.com/assets/images/blog/rebble-homescreen.png) ![](https://developer.repebble.com/assets/images/blog/rebble-subreddits.png) ![](https://developer.repebble.com/assets/images/blog/rebble-text.png) Read Reddit right on your phone, including support for custom subreddits and text AMAs. #### [BetsOn](http://betsonapp.com/) Built by [@aviromanoff](http://twitter.com/aviromanoff), [@sawyeraltman](http://twitter.com/sawyeraltman) and [@imjaredz](http://twitter.com/imjaredz) ![](https://developer.repebble.com/assets/images/blog/betson-handshake.png) Make a wager with a friend using your mobile phone, then confirm and "seal the deal" by shaking hands while wearing Pebbles. To authenticate the handshake, BetsOn uses accelerometer, timing and location. #### [Pebblic Speaking](http://www.pebblicspeaking.com/) Built by [@JiexiCao](http://twitter.com/JiexiCao), [@toptierphot](http://twitter.com/toptierphot), He and [@mplewis](http://twitter.com/mplewis) Improve your public speaking with the Pebblic Speaking app, which lets you receive key notecard-like cues from your watch at preset times. The app could be a great way to practice your timing or know when to transition to the next topic during a public presentation. #### [Conquistador](http://pennapps2014s.challengepost.com/submissions/20786-conquistador) Built by [@Drunkingo](http://twitter.com/Drunkingo), [@schmitzoide](http://twitter.com/schmitzoide), Ricardo and Paulo. ![](https://developer.repebble.com/assets/images/blog/conquistador-iphone.png) ![](https://developer.repebble.com/assets/images/blog/conquistador-marking-point.png) ![]() Play a real world conquest game with Conquistdor by claiming areas for your team (before the other team does the same). Quite a complicated application for just 40 hours! #### [Pebmo](https://apps.getpebble.com/applications/530d45b3ae03dcd9cf000087/) and [VenMoMoney](http://www.venmomoneymoproblems.com/) Pebmo built by [@andyveloper](http://twitter.com/andyveloper), VenMoMoney built by [@kt\_seagull](http://twitter.com/kt_seagull), [@\_ankushg](http://twitter.com/_ankushg), [@carolynz](http://twitter.com/carolynz) and Qui. ![](https://developer.repebble.com/assets/images/blog/pebmo-choose-friends.png) ![](https://developer.repebble.com/assets/images/blog/pebmo-choose-amount.png) ![](https://developer.repebble.com/assets/images/blog/pebmo-complete.png) ![](https://developer.repebble.com/assets/images/blog/venmomoney-balance.png) There are now not one but two (count 'em) unofficial [Venmo](http://venmo/com) clients for Pebble, allowing users to easily send money to friends nearby, and (in VenMoMoney's case) approve requests for money right on the Pebble. [![](http://pblweb.com/badge/530d45b3ae03dcd9cf000087/white/medium/)](https://apps.getpebble.com/applications/530d45b3ae03dcd9cf000087) Thanks again for having us at PennApps! Hope to see many of you at our next events this season, [HackDuke](http://www.hackduke.com) and [Hack Illinois](http://www.hackillinois.org/). * * * --- # CloudPebble now supports Simply.js Source: https://developer.repebble.com/blog/2014/03/14/CloudPebble-now-supports-Simplyjs/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 14 March 2014 - [Katharine Berry](https://developer.repebble.com/blog/authors/katharine/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [CloudPebble](https://developer.repebble.com/blog/tags/cloudpebble/) - [Comments](https://developer.repebble.com/blog/2014/03/14/CloudPebble-now-supports-Simplyjs.md#comments) It's now even easier to get started with Pebble development or prototype new ideas! CloudPebble recently gained support for building Simply.js projects – which means you can now write, build and install simple apps entirely in JavaScript and inside your web browser. To get started, [head to CloudPebble](https://cloudpebble.repebble.com/) and create a Simply.js project: ![Create Simply.js project](https://developer.repebble.com/assets/images/blog/cloudpebble-simplyjs-create.png) Once you've created the project you will be presented with a project containing a single file, `app.js`. This file will contain the standard Simply.js demo program, which demonstrates reacting to button clicks and accelerometer events. You can modify this script to contain anything you like; you can find some documentation and examples on the [Simply.js website](http://simplyjs.io/). We'll handily highlight lines with syntax errors as you type your code. Once you've written your app, you can easily get it on your watch. Head to the compilation page and hit the blue Run build button. In a couple of seconds you'll be able to install your new app! Make sure you have Wi-Fi enabled on your phone, enable developer mode, and enter the IP the app presents into the box. Then just hit Install & Run and you're set! Each time you change your Javascript code, you'll need to save the file, build it, and install. Once your app is installed, you can use the View app logs button to view any output from your `console.log` statements. And behold! Your own app, entirely in JavaScript: ![Simply Demo!](https://developer.repebble.com/assets/images/blog/cloudpebble-simplyjs-watch-screenshot.png) You can install as many of these CloudPebble/Simply.js apps as you can fit on your watch. Have fun! * * * --- # Pebble App Challenge Source: https://developer.repebble.com/blog/2014/03/10/Pebble-App-Challenge/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 10 March 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2014/03/10/Pebble-App-Challenge.md#comments) Dear Developers, We are celebrating the [launch of the Pebble appstore on Android](https://blog.getpebble.com/2014/03/07/the-pebble-appstore-now-with-android-support-and-more-partners/) by announcing the **Pebble App Challenge** , featuring a $5000 grand prize for the first place app developer(s) and a Pebble Steel for each of the Top 16 finalists. The Pebble App Challenge starts today. [Enter your Pebble application(s) in the challenge now!](http://pebble.challengepost.com/) **Submission period ends on March 23rd, 5pm PST.** Between March 24th and March 27th developers, Pebble users, and the public at large, will vote on their favorite submission on the [Pebble App Challenge site](http://pebble.challengepost.com). The Top 16 apps will be declared finalists and move on to the final rounds of the competition! ![Pebble App Challenge](https://developer.repebble.com/assets/images/blog/pebble-brackets.png) The first week of April, the Top 16 finalists will be paired and compete for votes from Pebble users only. Each app will play one match per day, and only the app with the most votes moves on to compete the next day. Matches will take place on the homepage of the Pebble appstore. [Enter the Pebble App Challenge today!](http://pebble.challengepost.com) Answers to a few common questions: - Can I enter the competition with an existing app? Yes. The challenge is opened to all the apps available in the Pebble appstore. - Can I submit multiple apps in the Challenge? Yes you can. - What type of apps are eligible? The challenge is opened to watchapps, watchfaces and companion apps. Your app must use the Pebble SDK 2.0 and be submitted on the Pebble appstore. - Can I enter the Challenge with a paid apps? Yes you can. We would recommend making it free during the contest to increase your chance of success. More details is available on the [Pebble App Challenge site](http://pebble.challengepost.com). As always feel free to ask more questions here in the comments, on [our forums](https://forums.getpebble.com/categories/developer-discussion) and to [@pebbledev](https://twitter.com/pebbledev) Cheers! * * * --- # Android Beta11 Source: https://developer.repebble.com/blog/2014/03/03/Android-Beta11/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 March 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/03/03/Android-Beta11.md#comments) Android Beta 11 was just released. This version is a huge improvement to BETA10, it fixes the most annoying bugs in BETA10 but also improves stability considerably. Changed in this version: - PebbleKit JS localStorage is now sandboxed for each app - Many improvements to onboarding and FW installation - Issues with sideloading apps in some circumstances have been fixed - Some issues with loading JS configuration views after the app has been quit have been addressed - JavaScript app logs printed via ADB are cleaner - MyPebble now loads faster Known issue: - Pairing a Pebble during onboarding may not succeed. If you run into problems, exit the Pebble app. Remove the pairing entry from Android BT settings & your Pebble. Then pair manually using BT settings. Please report problems and bugs in [the forums](https://forums.getpebble.com/discussion/11651/pebble-2-0-android-beta11-feedback). Have a great day! * * * --- # Installing the Pebble SDK just got simpler Source: https://developer.repebble.com/blog/2014/02/21/oneline-sdkinstall/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 21 February 2014 - [Alexey Komissarouk](https://developer.repebble.com/blog/authors/alexey/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/02/21/oneline-sdkinstall.md#comments) Until today, installing the Pebble SDK meant downloading and unzipping the SDK and arm tools, installing some python requirements and updating your bash\_profile. ### New and Improved We've been working to make it easier to get started with the Pebble SDK. Starting today for OS X users, installing the SDK is as simple as running > Since the release of SDK 3.0 we have removed the SDK installation script. Sorry for the inconvenience. in your terminal. Of course, the [manual install guide](https://developer.repebble.com/sdk/install/mac/) for OS X is still available if you run into any issues or are more comfortable installing the requirements manually. ### Caveats The Pebble SDK requires Python 2.7, as well as OS X 10.6 or later (10.8 or later is recommended). Support for automatic installation on linux distributions is on our roadmap as well. Try out the one-line SDK install and let us know if you run into any issues by posting on the [SDK Installation Forum](https://forums.getpebble.com/categories/sdk-install) or [contacting us](https://developer.repebble.com/contact.md). #### Note > As part of the change, we now install our python dependencies through virtualenv rather than globally on your system, which should reduce the number of issues during installation. * * * --- # Android Beta10 Source: https://developer.repebble.com/blog/2014/02/19/Android-Beta10/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 19 February 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/02/19/Android-Beta10.md#comments) **Update:** [A new version is available](https://developer.repebble.com/blog/2014/03/03/Android-Beta11.md). Dear Developers, We just published Pebble Android 2.0 Beta 10. We are getting close to a final release and you can help by testing this build on your devices. This is what changed in this version: - Improvements to speed in MyPebble - Improvements to connection stability (this fixes the appmessage getting stuck bug) - Improvements to power use - Installing apps via websockets will now auto launch them - Improvements to JavaScript launch in many conditions, such as when the watch just lost connection to the phone - Improvements to pairing during onboarding Known problems: - Still some issues in onboarding. - Side-loading watchapps and watchfaces does not work. - HTML5 localStorage gets shared between apps. - The onboarding process will restart if you enable Accessibility. Workaround: Do not enable accessibility in the onboarding process. - If you are upgrading from a pre-2.0 firmware, you will need to first upgrade your watch to 1.14. Upgrading from earlier version is not fully supported at the moment. (To upgrade your Pebble firmware, please use the Pebble 1.9.6 app available in Google Play). Please install this version and report problems and bugs in [the forums](https://forums.getpebble.com/discussion/10876/pebble-2-0-android-beta-feedback#latest). * * * --- # New Android Beta Source: https://developer.repebble.com/blog/2014/02/10/New-Android-Beta/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 10 February 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/02/10/New-Android-Beta.md#comments) **Update:** [A new version is available](https://developer.repebble.com/blog/2014/03/03/Android-Beta11.md). We just released a new version of Pebble Android 2.0 Beta with the Pebble appstore. This is what changed in this version: - Fix the "Settings" button. Will only appear for configurable apps and will always work. - Removed the phone popup when installing apps via the developer connection. - Added logs to better understand the battery drain problems. - (Updated) `getAccountToken()` now works on Android Please install this version and report problems and bugs in [the forums](https://forums.getpebble.com/discussion/10876/pebble-2-0-android-beta-feedback#latest). Here is a list of known issues in this version: - Appmessage blocks after a few minutes: We have a fix for this but we are still testing it. It will be included in the next version. - You may have trouble pairing if you have lots of bluetooth devices around. - After the few "onboarding screens", the app will exit and you will need to retart it. - After installing an app with the `pebble` tool, you will need to manually start the app Please come back to this list regularly. We will update it with new known problems. * * * --- # Android appstore beta Source: https://developer.repebble.com/blog/2014/02/03/Android-appstore-beta/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 February 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/02/03/Android-appstore-beta.md#comments) **Update:** [A new version is available](https://developer.repebble.com/blog/2014/03/03/Android-Beta11.md). This morning [we released the Pebble appstore for iOS](https://developer.repebble.com/blog/2014/02/03/Pebble-appstore-is-live.md). We love Android and feature parity is extremely important for us. We are releasing today a beta of the Pebble Android mobile app that includes the Pebble appstore to all developers. You can help us finalize the Android app and ship it to everyone very quickly: - Report bugs Please install it on your device, use it to download apps from the Pebble appstore and share any bug you might encounter. **Update** : We have opened a dedicated forum thread to discuss the Android Beta. [Join the discussion!](https://forums.getpebble.com/discussion/10876/pebble-2-0-android-beta) - Refer your Android developer friends to Pebble! Do you know any great Android developers? Tell them about Pebble, tell them how much you would appreciate them joining us to make a real difference in your life! Seriously, we are hiring: [please apply!](http://pages.getpebble.com/pages/jobs). This is a BETA release, and comes with some known issues that we are working on: - The pairing process has some issues. We recommend first pairing in the bluetooth settings of your phone and then starting the pebble app. - You will not be able to configure an app if it is running on your Pebble. Exit the app (or watchface) before clicking the settings button. - When installing an application with the pebble tool, you will need to press the "Load" button on your phone to proceed with the install. Make sure the app is launched and in the foreground before you run `pebble install`. - `getAccountToken()` currently returns an hardcoded token. - If your are an Android developer, you might have the Android dev options of "Limit Background Processes" and "Don't Keep Activities" checked. You need to uncheck/disable these to use the Pebble app. - The mobile app shows a "Settings" icon for all JavaScript apps, even if they are not configurable. Please come back to this list regularly. We will update it with new known problems. **Update:** [A new version is available](https://developer.repebble.com/blog/2014/03/03/Android-Beta11.md). * * * --- # Pebble appstore is live! Source: https://developer.repebble.com/blog/2014/02/03/Pebble-appstore-is-live/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 February 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/02/03/Pebble-appstore-is-live.md#comments) ![](https://developer.repebble.com/assets/images/blog/available-on-ios.png) This is a big day for Pebble developers and the wearable ecosystem in general! Almost one year after the first version of Pebble SDK, this morning we have released a new version of the Pebble iOS app including the Pebble appstore. For the first time in the history of wearable computing, users can browse and download third party apps: **your apps**. This is an important milestone. First and foremost we want to thank you for your journey with us. In just 3 months since the first beta of SDK 2 you have blown us away with your talent and creativity! Here are some answers to the most pressing questions: - When will Android users get the Pebble appstore? - Can I see how many people have downloaded my application? Not yet, but we are collecting analytics and we'll make them available in the [developer dashboard](http://dev-portal.getpebble.com) very soon. - When is the next update of JavaScript apps coming? Tonight (that is Feb 3rd 2014 at 8pm PST) we will do another sweep of JavaScript apps and include them in version 2.0.1 of the iOS app. Please make sure you push to the appstore and publish your updated PBWs if you need to fix any bugs. You should not remove your previous releases. The next update will come 24 hours after the publication of the 2.0.1 iOS app. - What version of the iOS app should I use for development? You should now delete any BETA version of the Pebble iOS app and use the app available in the Apple App Store for development. It includes the developer connection to deploy your apps to your Pebble. Remember to enable it in the iOS Settings. - Known bug in DataLogging There is one known issue in the 2.0.0 Pebble iOS app with datalogging. To make sure this bug is not triggered, we have disabled the datalogging APIs in the 2.0.0 firmware. Launching apps using these APIs will exit the app and show a message "\ is not supported". We will re-enable datalogging when the 2.0.1 iOS app is available on the App Store. - PebbleKit iOS applications We have released a [new version of PebbleKit iOS in SDK 2.0.0](https://developer.repebble.com/sdk/changelogs/2.0.0.md). You can use this version for development, it now supports armv7s, arm64 and x86\_64 architectures. **Please do not use this version of PebbleKit to submit an application to Apple.** There is a known bug that can cause your application to crash when it's in the background. It will be fixed in an upcoming SDK release. Join us on the [developer forums](https://forums.getpebble.com/categories/developer-discussion) to discuss any problems or share questions. If your question must remain private, you can [contact us](https://developer.repebble.com/contact.md). We are also listening on [@pebbledev](https://twitter.com/pebbledev). Cheers! Cherie & Thomas - On behalf of Team Pebble * * * --- # Tips for Appstore Success Source: https://developer.repebble.com/blog/2014/01/31/Appstore-Tips/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 31 January 2014 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/01/31/Appstore-Tips.md#comments) If you have already published an app, or are planning on publishing an app, here are some tips for a successful appstore launch. - If your Pebble app requires an iOS or Android companion app, make sure that you have entered the Apple App Store/Google Play URL correctly for your app. Double-check that your links work by clicking on the companion app name in the developer dashboard or clicking the link in the mobile appstore. ![](https://developer.repebble.com/assets/images/blog/iOS_companion.png) ![](https://developer.repebble.com/assets/images/blog/android_companion.png) * * * --- # Pebble SDK 2.0 BETA7 Source: https://developer.repebble.com/blog/2014/01/23/Pebble-SDK-BETA7/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 23 January 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/01/23/Pebble-SDK-BETA7.md#comments) Attention developers! Pebble SDK 2.0 BETA7 [just landed on the developer site](https://developer.repebble.com/sdk/download)! Go get it now and [do read the release notice](https://developer.repebble.com/sdk/changelogs/2.0-BETA7.md). iOS users will need to delete any existing Pebble app before they can upgrade. Highlights of this new version: - More random crashes fixed in the firmware - Seriously improved datalogging on iOS (and some bugfixes on Android) - Fixed the URL scheme to install Pebble applications. It did not work in Beta6. - Added support for `getAccountToken()` in PebbleKit JS (iOS only at the moment) - iOS application and PebbleKit iOS are now 64-bit compatible - iOS application does not crash on iPhone 4 anymore - Some breaking changes in PebbleKit iOS: We cannot use NSNumber categories in 64 bit because their size is unknown. We added a new PBNumber class. This class is returned if you use the NSNumber Pebble category. The activity on the forums is at its peak and we have been killing bugs like crazy! Please keep them coming! We have never been so close to a release! We will have another Pebble [virtual lab](https://pebbledev-virtuallab-0120.eventbrite.com) tomorrow morning from 9 to 12 PST (17 to 20 UTC). Join us! IRC Channel: #pebble on freenode.net. [Download Pebble SDK 2.0 BETA7](https://developer.repebble.com/sdk/download) ## Pebble appstore checklist Have you already submitted your app? Or are you about to submit? Here is a quick checklist for you: - Is your application in the 'Published' state? Publishing a release is not enough. You need to click the 'Publish' button in the top right corner to make sure your app is visible. Please double-check this! - If you had a 1.x version of your app, and did you provide the name and UUID of your 1.x app in the developer dashboard? This will allow us to automatically migrate your users to the 2.0 version of your app. - Did you upload a header image for your app (not required for watchfaces)? If not, we provide the [Pebble appstore marketing materials](https://developer.repebble.com/guides/appstore-publishing/appstore-assets/) to help you. - Did you upload at least one screenshot of your app? You can easily create a screenshot of your app by running 'pebble screenshot --phone \' or by selecting "Screenshot" from the compilation screen of [CloudPebble](https://cloudpebble.repebble.com/) ## In other news - We will have a [Pebble Virtual Lab](http://pebbledev-virtuallab-20140131.eventbrite.com) session next Friday from 2PM to 5PM PST. This is a great opportunity for you to ask all of your burning questions! - The [developer forums](https://forums.getpebble.com/categories/developer-discussion) are the best place to get assistance. You can also [contact us](https://developer.repebble.com/contact.md) if you can't find an answer on the forums. * * * --- # Pebble SDK 2.0 BETA6 - Season Finale? Source: https://developer.repebble.com/blog/2014/01/17/Pebble-SDK-BETA6/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 17 January 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/01/17/Pebble-SDK-BETA6.md#comments) Another week and another release of Pebble SDK 2.0. In this version you will find that the iOS application now includes the Pebble appstore and will give you a chance to see how your application looks and feels with the new store. If you had a 1.x application, you should also make sure that the automatic upgrade works properly for your app. Beta6 also brings more bugfixes to the Pebble firmware and you will find that it is more stable than previous releases. [Read the full Changelog](https://developer.repebble.com/sdk/changelogs/2.0-BETA6.md). > **Attention iOS Users:** > > - Before you install this version, you need to remove the Pebble app from your phone. You will need to reinstall all your JavaScript apps afterwards. > - iPhone5S, iPad Air and Retina iPad Mini users will need to manually pair in the Settings of the phone. [Download Pebble SDK 2.0 BETA6](https://developer.repebble.com/sdk/download/) ## JavaScript application deadline updated We have postponed the JavaScript application deadline to Sunday January 19th at 11:59 pm PST. You have a bit more time to get your app in the store! We got a lot of questions on the JavaScript/iOS deadline. We are glad to see so much interest! Here are the most common questions and answers: - What do you mean by "the new iOS application will embed the source of all JavaScript apps available in the appstore"? - If I upload a new version of my application with a new binary and a new JavaScript file, will my users get the new binary right away? - If I miss the first deadline, how long will I have to wait for another update? - Will the Pebble application allow users to download JavaScript apps from the web? - As a developer, how will I be able to test and debug my application? ## Pebble appstore checklist Have you already submitted your app? Or are you about to submit? Here is a quick checklist for you: - Is your application in the 'Published' state? Publishing a release is not enough. You need to click the 'Publish' button in the top right corner to make sure your app is visible. Please double-check this! - If you had a 1.x version of your app, did you provide the name and UUID of your 1.x app in the developer dashboard? This will allow us to automatically migrate your users to the 2.0 version. - Did you upload a header image for your app (not required for watchfaces)? If not, we provide the [Pebble appstore marketing materials](https://developer.repebble.com/guides/appstore-publishing/appstore-assets/) to help you. ## Deep links to Pebble applications You can link directly to a Pebble app or watchface in the Pebble appstore. Simply grab your Pebble application identifier in the URL of the developer dashboard and append it to: `pebble://appstore/`. For example, if your application in the portal has the URL `https://dev-portal.getpebble.com/applications/52d396683e2e256b6c00001e`, then a deep-link for your app is `pebble://appstore/52d396683e2e256b6c00001e`. This will work on iOS and Android, as long as the user has the 2.0 Pebble application installed. Installing applications through the Pebble appstore has many benefits: - A better user experience, and one that is consistent with searching and installing from the store. - Your application will be added to the Locker (The Pebble locker is a list of applications a user can easily swap in and out.) - You, as a developer, will get detailed stats on downloads and usage of your application. - We will automatically upgrade your app on Pebble when you publish an upgrade. We strongly recommend you switch to this new model instead of directly installing PBWs. ## In other news - We updated the [Design guide](https://developer.repebble.com/guides/appstore-publishing/appstore-assets/) with a section on designer resources that you can use to promote your Pebble applications. - If you are not using JSHint now, you need to read our blog article on [Setting up JSHint for Pebble Development](https://developer.repebble.com/blog/2014/01/12/Using-JSHint-For-Pebble-Development.md) - We will have a [Pebble Virtual Lab](http://pebbledev-virtuallab-0120.eventbrite.com) session next friday from 9 am to 12 PST, that is 16 to 20 UTC. Hopefully this timeframe will be more practical for those of you in Europe. - The [developer forums](https://forums.getpebble.com/categories/developer-discussion) are the best place to get assistance. You can also [contact us](https://developer.repebble.com/contact.md). - We’re still hiring like crazy right now and we’d love to have you come join us! Pebble is a fantastic place to work and there are countless opportunities for you to grow within the organization. Check out our [jobs page](https://www.getpebble.com/pages/jobs) for more details. * * * --- # Using JSHint for Pebble Development Source: https://developer.repebble.com/blog/2014/01/12/Using-JSHint-For-Pebble-Development/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 12 January 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/01/12/Using-JSHint-For-Pebble-Development.md#comments) In our post on [JavaScript Tips and Tricks](https://developer.repebble.com/blog/2013/12/20/Pebble-Javascript-Tips-and-Tricks.md) we strongly recommended the use of JSLint or [JSHint](http://www.jshint.com/) to help you detect JavaScript problems at compile time. We believe JSHint can really increase the quality of your code and we will probably enforce JSHint correct-ness for all the apps on the appstore very soon. In this post we show you how to setup jshint and integrate it in your Pebble development process. ## Installing JSHint JSHint is a modern alternative to JSLint. It is more customizable and you can install it and run it on your machine. This allows you to run JSHint every time you build your Pebble application and fix errors right away. This will save you a lot of install/crash/fix cycles! JSHint is a [npm package](https://npmjs.org/package/jshint). NPM is included in all recent NodeJS installations. On Mac and Windows you can [download an installer package](http://nodejs.org/download/). On most Linux distribution, you will install nodejs with the standard package manage (`apt-get install nodejs`). To install the JSHint package, run this command: ``` sudo npm install -g jshint ``` The `-g` flag tells npm to install this package globally, so that the `jshint` command line tool is always available. ## Configuring jshint JSHint offers a large selection of options to customize its output and disable some rules if you absolutely have to. We have prepared a default configuration file that we recommend for Pebble development. The first half should not be changed as they are the rules that we may enforce on the appstore in the future. The second half is our recommended best practices but you can change them to suit your coding habits. Save this configuration in a `pebble-jshintrc` file in your Pebble project. ``` /\* \* Example jshint configuration file for Pebble development. \* \* Check out the full documentation at http://www.jshint.com/docs/options/ \*/{// Declares the existence of a global 'Pebble' object"globals": { "Pebble" : true },// And standard objects (XMLHttpRequest and console)"browser": true,"devel": true,// Do not mess with standard JavaScript objects (Array, Date, etc)"freeze": true,// Do not use eval! Keep this warning turned on (ie: false)"evil": false,/\*\* The options below are more style/developer dependent.\* Customize to your liking.\*/// All variables should be in camelcase"camelcase": true,// Do not allow blocks without { }"curly": true,// Prohibits the use of immediate function invocations without wrapping them in parentheses"immed": true,// Enforce indentation"indent": true,// Do not use a variable before it's defined"latedef": "nofunc",// Spot undefined variables"undef": "true",// Spot unused variables"unused": "true"} ``` ## Automatically running jshint To automatically run jshint every time you compile your application, you will need to edit your `wscript` build file. This is the modified `wscript` file: ``` # Use the python sh module to run the jshint commandfrom sh importjshinttop='.'out='build'def options(ctx):ctx.load('pebble\_sdk')def configure(ctx):ctx.load('pebble\_sdk')# Always pass the '--config pebble-jshintrc' option to jshintjshint.bake(['--config','pebble-jshintrc'])def build(ctx):ctx.load('pebble\_sdk')# Run jshint before compiling the app.jshint("src/js/pebble-js-app.js")ctx.pbl\_program(source=ctx.path.ant\_glob('src/\*\*/\*.c'),target='pebble-app.elf')ctx.pbl\_bundle(elf='pebble-app.elf',js=ctx.path.ant\_glob('src/js/\*\*/\*.js')) ``` Now simply run `pebble build` and if there is anything wrong in your JS file, the build will stop and the errors will be displayed on the console. You won't believe how much time you will save! ## CloudPebble users CloudPebble users are encouraged to download and install JSHint on their machine. You can use the [web interface](http://www.jshint.com/) and set most of the recommended options but you will never get 100% correctness because there is no way to declare an extra global variable (the `Pebble` object). Good news is [CloudPebble is opensource](https://github.com/CloudPebble/CloudPebble/)! We would be happy to offer a brand new [Pebble Steel](http://www.getpebble.com/steel) to the developer who will submit and get through Katherine's approval a pull request to add jshint support in CloudPebble ;) * * * --- # Pebble SDK 2.0 BETA5 is now available! Source: https://developer.repebble.com/blog/2014/01/10/Pebble-SDK-BETA5/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 10 January 2014 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/01/10/Pebble-SDK-BETA5.md#comments) We've had a pretty awesome start to 2014 and we couldn't have done it without you! First, we kicked off the year by sponsoring the wildly successful [AT&T Developer Summit hackathon](https://devsummit.att.com/), which was followed shortly after by our announcement of the new [Pebble Steel](https://rePebble.com/steel) during [CES](https://www.cesweb.org/). In addition, we announced that 2.0 will be publicly released along with the Pebble appstore at the end of January. Have you [submitted your Pebble app](https://dev-portal.getpebble.com/) yet? To prepare for the 2.0/appstore launch, we're releasing BETA5, an update that will make your apps run better than ever and will fix a lot of bugs that you've reported to us! This version brings us critical fixes and improvements to the SDK and mobile apps: - Fixed Android data logging bugs where data would get duplicated - Merged data logging fixes for iOS that were supposed to be in BETA4 (sorry!) - Added an end of session message on Android data logging - Fixed accelerometer bugs where the accelerometer would stop sending data - Changed the animation when switching from one watchface to the next - ...and more! [Read the Full Changelog / Release Notes](https://developer.repebble.com/sdk/changelogs/2.0-BETA5.md) Pebble 1.x apps are not compatible with Pebble SDK 2.0, but if you upload a 2.0 build of your application, we will automatically upgrade your users to the new version. To take advantage of this, you just need to submit your app to the [Pebble appstore](https://dev-portal.getpebble.com/) and add the Name and UUID of your 1.x apps. Still having some issues and need some attention from the Pebble Developer Support team? Make sure to sign up for next week's [Open Lab](http://pebbledev-openlab-0117.eventbrite.com)! Not in the SF Bay Area? Sign up for our [Virtual Lab](http://pebbledev-virtuallab-0120.eventbrite.com) the following week! Please don’t hesitate to [contact us](https://developer.repebble.com/contact.md) if you have any questions on the SDK. > We’re also hiring like crazy right now and we’d love to have you come join us! Pebble is a fantastic place to work and there are countless opportunities for you to grow within the organization. Check out our [jobs page](https://www.getpebble.com/pages/jobs) for more details. We hope the new year is off to a great start for you, and we can't wait to see where you take Pebble in 2014! * * * --- # Writing Pebble Apps On The Plane Source: https://developer.repebble.com/blog/2014/01/08/Writing-Pebble-apps-on-the-plane/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 08 January 2014 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2014/01/08/Writing-Pebble-apps-on-the-plane.md#comments) ![](https://developer.repebble.com/assets/images/blog/pebble-on-plane.jpg) I love working on the plane. It is one of those rare place where I can spend 8 hours coding without being disturbed by emails, phone, etc. Here are a few tips to write Pebble apps in the air. ## 1. Download and Install the SDK [CloudPebble](https://cloudpebble.repebble.com/) is awesome but unfortunately it will not work when you are not connected (and if you are using in-flight wifi, you will likely be disturbed by the usual suspects mentioned above). Installing the Pebble SDK on a Mac or a Linux computer is very easy and should only take you a few minutes [following our instructions](https://developer.repebble.com/sdk.md). Make sure you do this before you get on the plane as some commands require Internet access. ## 2. Use the documentation packaged in the SDK You may not know this but your local SDK install contains a full copy of the [API Reference](https://developer.repebble.com/docs/c.md). You can open it directly from the filesystem in your browser: `file:///Users/thomas/pebble-dev//Documentation/pebble-api-reference/modules.html`. Another great way to read the documentation is to use [Dash](http://kapeli.com/dash) and the [Pebble doc bundle](http://partlyhuman.github.io/PebbleSdkDocset/Pebble.xml) provided by [Roger Braunstein](https://github.com/partlyhuman/PebbleSdkDocset). You also get a full copy of the examples in `/Examples`. ## 3. Create your own local network You really should use the `pebble` tool to push apps on your Pebble and debug them. To do that you need a wifi connection and the ability for different devices to communicate. I have tried a few options. - Using an hotspot on the plane If your plane is equipped with GoGo InFlight, just connect to the Wifi network. You do not need to pay. Your laptop and your mobile phone will get a local IP address and in my experience they can communicate perfectly with each other. - Create a hotspot from your phone On iOS this only works if you have the option enabled in your plan. Turn-on the "Personal Hotspot" and use Bluetooth, USB, or Wifi, to connect your laptop to your phone. Even in airplane mode, this works. On Android use the "Tethering & Portable HotSpot" menu in the Settings to create a new hotspot. This is very reliable and will also allow you to connect an iOS device. Some Android devices only allow this if the airplane mode is off which of course is not an option. - Create your own hotspot from your laptop If there is no hotspot on the plane, you can create your own. On a Mac, just click on the Wifi icon, then _Create Network..._ and choose a name and password. Connect your mobile to that network. Because you do not have a DHCP server, both the computer and the phone will wait for a bit before they automaticall assign IP address (in the form 169.254.xxx.xxx). To speed things up, you can use static addresses (192.168.0.1 and 192.168.0.2 for example). This seems to only work with iOS. Unfortunately Android devices cannot connect to an Ad-Hoc wifi network. Once the Wifi is up, you should be able to turn on the developer connection and use the `pebble` tool with the `--phone 192.168.x.x` option to ping your Pebble: ``` $ pebble ping --phone 192.168.43.1 ```   Enjoy! * * * --- # A once in a lifetime opportunity: Pebble appstore launch Source: https://developer.repebble.com/blog/2014/01/06/Pebble-appstore/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 06 January 2014 - - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2014/01/06/Pebble-appstore.md#comments) Dear Developers, Have you submitted your apps to the Pebble appstore? [Eric announced this morning at CES](https://blog.getpebble.com/?p=187) that the appstore will be officially released in late January. The 2.0 BETA period is coming to an end; users will soon get their hands on the amazing apps you have created. Eric also announced a brand new Pebble: [Pebble Steel](https://rePebble.com/steel). We have a few to give away to the developer community. Keep reading for more information! ![Pebble Steel](https://developer.repebble.com/assets/images/blog/pebble-steel-1trio.png) Appstore launches do not happen often and we all know this is a big opportunity; the first apps on the appstore will receive more downloads and attention than apps submitted at a later date. To prepare for this big day, we would like to share some tips and information: ## Submitting an application to the Pebble appstore Submitting an application is a free and very easy process that only takes a few minutes. Just head to our [developer dashboard](https://dev-portal.getpebble.com) and upload your application and a few images. We have prepared some resources to help you: - [Publishing to the Pebble appstore guide](https://developer.repebble.com/guides/appstore-publishing.md) - [Photoshop template for Pebble appstore assets](https://developer.repebble.com/guides/appstore-publishing/appstore-assets/) - [Answer to some common questions](https://developer.repebble.com/blog/2013/12/18/All-Aboard-the-Pebble-app-store.md) - [How to take screenshots with the `pebble` tool](https://developer.repebble.com/guides/tools-and-resources/pebble-tool.md) If you are new to Pebble SDK 2.0 or need to upgrade (the latest release is BETA4), you will need: - [Pebble 2.0 SDK bundle](https://developer.repebble.com/sdk.md) - [Pebble 2.0 Firmware images](https://developer.repebble.com/sdk.md) - [Pebble 2.0 iOS app](https://docs.google.com/a/pulse-dev.net/forms/d/14r3MHPsdH5ha-BCkfuquQuAKuQSEJLmxm--XXpBA8mg/viewform) (some info required, you will receive a download link shortly) - [Pebble 2.0 Android app](https://developer.repebble.com/sdk.md) ## Automatic migrations When users install the new Pebble mobile app, the list of apps installed on their Pebble will be downloaded and saved on the phone. The firmware will be automatically upgraded to 2.0 and the Pebble app will look for and install the 2.0 version of those apps from the Pebble appstore. Matching of applications for the automatic upgrade is done on two criteria: Name and UUID. If you have written and published a Pebble 1.x application, make sure that you publish the 2.0 version of your app on the Pebble appstore and register the old name and old uuid of your 1.x app. If you are replacing several 1.x apps by one 2.0 app (this is often the case for watchfaces that had several variants and are now [configurable](https://developer.repebble.com/blog/2013/11/21/Using-PebbleKit-JS-Configuration.md)) you can provide several pairs of UUIDs and names. > Please note that the UUID you need to provide in the store is a standardized UUID in the form: `550e8400-e29b-41d4-a716-446655440000`. UUID of 1.x apps were defined as an array of bytes, for example: `0x42, 0x33, 0x22, ...`. In both cases they are a 16 byte identifier and conversion from one format to the other is trivial; simply remove the `0x` prefixes and commas, and add the dashes. > > For example the httpPebble UUID was: `{ 0x91, 0x41, 0xB6, 0x28, 0xBC, 0x89, 0x49, 0x8E, 0xB1, 0x47, 0x04, 0x9F, 0x49, 0xC0, 0x99, 0xAD }` > > In the appstore format, it should be written `9141B628-BC89-498E-B147-049F49C099AD`. ## Deadline for JavaScript applications As previously mentioned in [the appstore announcement](https://developer.repebble.com/blog/2013/12/18/All-Aboard-the-Pebble-app-store.md), the new iOS application will embed the source of all JavaScript apps available in the appstore. When iOS users browse the Pebble appstore, only the apps that have been bundled in the Pebble mobile app will appear (non-Javascript apps will appear normally). **We will extract the JavaScript of all the appstore apps on January 9th at 23:59 PST.** If you are using PebbleKit JavaScript in your app, make sure your app is published on the store by this time. ## Beta versions and reporting bugs We will continue releasing BETA versions of the 2.0 firmware and mobile apps as we have in the past. Your help finding and fixing bugs is extremely appreciated. The forums are a great way to work together with other Pebble developers, so please consider posting there first. We review every post in the [Developer Discussions](https://forums.getpebble.com/categories/developer-discussion). Two tips that go a long way towards helping us fix bugs faster: 1. If you encounter a bug or a crash, use the "Contact Support" button in the mobile apps to extract logs from your Pebble. 2. Make sure you provide very clear reproduction steps, along with the simplest code sample that can reproduce the issue. ## CloudPebble update [@KatharineBerry](https://twitter.com/KatharineBerry) just pushed a [major update to CloudPebble](http://blog.cloudpebble.net/2013/12/sdk-2-0-developer-mode/) for SDK 2.0, so if you haven't used CloudPebble in awhile, definitely take some time to check out the new changes! ![CloudPebble installing app](http://blog.cloudpebble.net/wp-content/uploads/2013/12/Screen-Shot-2013-12-25-at-12.05.27.png) CloudPebble now allows you to input your phone IP address and install an application over the air from your browser. You can also view a console with realtime logs from your app. ![CloudPebble logs](http://blog.cloudpebble.net/wp-content/uploads/2013/12/Screen-Shot-2013-12-25-at-12.09.05.png) This is an excellent way to get started, and by far the best option for Windows developers. [Give it a try!](https://cloudpebble.net/) ## Check out Pebble Steel! This update would not be complete without a word about [the new Pebble](https://rePebble.com/steel). In terms of development, it is completely compatible with existing apps (same screen, same CPU, same memory, same bluetooth chip, etc). The only extra hardware is the RGB LED. This new hardware is a beautiful piece of engineering and we want to share it with you our developers. We have reserved 4 Pebble Steels that we will send to the developer of: - One working JavaScript app selected at random from apps published on the appstore before January 9th - One working app selected randomly from published appstore apps that is a 1.x migration - One working app selected at random from all published apps on the appstore, including watchfaces - And the Pebble developer that sends the most useful bug report(s) prior to appstore launch (These bug reports must be posted to the forums under "Developer Discussion", with clear reproduction steps) _This giveaway is limited to one Pebble per developer, and we reserve the right to disqualify apps that are fraudulent or that do not work._ Thanks for making Pebble so #awesome in 2013 - We are very excited to start 2014 with all of you and wish you a very Happy New Year! Cherie and Thomas (for Team Pebble) * * * --- # Pebble Sdk Beta4 Source: https://developer.repebble.com/blog/2013/12/23/Pebble-SDK-BETA4/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 23 December 2013 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2013/12/23/Pebble-SDK-BETA4.md#comments) Busy times for the Pebble elves! Beta3 last week, [the app store announcement](https://developer.repebble.com/blog/2013/12/18/All-Aboard-the-Pebble-app-store.md) on wednesday and now a new BETA! [Pebble SDK BETA4 is available on our website now](https://developer.repebble.com/sdk/download). Existing iOS developers will receive an email with a link to install it; if you are not on that list yet, please [fill the form](https://docs.google.com/a/pulse-dev.net/forms/d/14r3MHPsdH5ha-BCkfuquQuAKuQSEJLmxm--XXpBA8mg/viewform) to request it. This version gets us even closer to an official 2.0 release with more fixes and improvements on the SDK and the mobile apps: - Fixed a problem where the iOS app would get killed after a few minutes in the background - Lots of DataLogging fix on the firmware and on Android - Added timestamps on accelerometer samples - Improved errors handling for PebbleKit JS apps on iOS and Android - [Read the full Changelog / Release Notes](https://developer.repebble.com/sdk/changelogs/2.0-BETA4.md) Pebble 1.x apps are not compatible with Pebble SDK 2.0 - but **if you upload a 2.0 build of your application, we will automatically upgrade your users to the new version**. To take advantage of this, you just need to submit your application in the [Pebble appstore](https://dev-portal.getpebble.com/) and add the Name and UUID of your 1.x apps. We've also got a special treat for our designer friends! Today we're adding a new document to the Pebble library, the [Pebble UX Design Guide](https://developer.repebble.com/guides/design-and-interaction.md). This guide is written for designers and soon-to-be-designers of Pebble apps who want to create a cohesive user experience that works with the latest Pebble SDK functionality. As always, please don't hesitate to [contact us](https://developer.repebble.com/contact.md) if you have any questions. We wish you a very pleasant time with family and friends - And please, please, please, **upload your apps to the Pebble appstore!**. #### Pebble JavaScript tips and tricks We posted a new blog post on our website yesterday with tips and tricks to make the most out of PebbleKit JavaScript. Make sure you [read it!](https://developer.repebble.com/blog/2013/12/20/Pebble-Javascript-Tips-and-Tricks.md) #### Last call! #ifihadpebble We're running the [#ifihadpebble challenge](https://developer.repebble.com/blog/2013/11/20/Win-A-Pebble.md) until December 25th, so submit your app ideas for a chance to earn a free Pebble! We will also be announcing a number of developer challenges in the near future -- stay tuned to the [developer blog](https://developer.repebble.com/blog) for the latest news! #### Open/Virtual Labs for Pebble Developers We've started hosting our [open (in-person) labs](https://developer.repebble.com/blog/2013/11/03/Developer-News-and-Reddit-and-Open-Lab.md) and [virtual (online) labs](https://developer.repebble.com/blog/2013/12/03/Virtual-Lab-For-Pebble-Developers.md) and are really excited about the feedback we've been receiving. Our plan is to continue to host these weekly -- check the [developer blog](https://developer.repebble.com/blog) for announcements of dates and other details. #### CES & AT&T Developer Summit Hackathon If you're going to [CES](https://www.cesweb.org/) in January 2014, please do get in touch! We'd love to say hi and get you some Pebble goodies. We'll also be working hard the weekend before CES at the [AT&T Developer Summit Hackathon](https://devsummit.att.com/); stop by for a chance to hack on the Pebble and to win some amazing wearables prizes! #### Come work with us at Pebble! We're hiring like crazy right now and we'd love to have you come join us! Pebble is a fantastic place to work and there are countless opportunities for you to grow within the organization. Check out our [jobs page](https://www.getpebble.com/pages/jobs) for more details. * * * --- # Pebble Javascript Tips And Tricks Source: https://developer.repebble.com/blog/2013/12/20/Pebble-Javascript-Tips-and-Tricks/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 20 December 2013 - team pebble - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2013/12/20/Pebble-Javascript-Tips-and-Tricks.md#comments) PebbleKit JavaScript is one of the most popular additions in Pebble SDK 2.0. We are already seeing lots of amazing apps being built that take advantage of http requests, configuration UI, GPS and local storage on the phone. Here are some Tips & Tricks to get the most out of JavaScript in your Pebble apps. ### Lint it! Use [JSLint](http://jslint.com/) ([GitHub](https://github.com/douglascrockford/JSLint)) to check that your Javascript is valid. JSLint with default settings has been successful in catching most of the JS errors that have been reported to us. If you lint it, there's a much better chance that your app will run smoothly on both the Android and iOS platforms. ### Make the best use of your data storage There are two types of storage that are used with the Pebble, persistent storage and local storage. [Persistent storage](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") is available through a C API and saves data on Pebble. [Local storage](https://developer.repebble.com/guides/communication/using-pebblekit-js.md) is a JavaScript API and saves data on the phone. These two long-term storage systems are not automatically connected and you will need to use the [AppMessage](https://developer.repebble.com/docs/c/Foundation/AppMessage.md "AppMessage") or [AppSync](https://developer.repebble.com/docs/c/Foundation/AppSync.md "AppSync") API's to transfer data from one to the other. Persistent storage is a key-value storage where the values are limited to 256 bytes and the total space used cannot exceed 4 kb. Persistent storage is not erased when you upgrade the app - but it is erased if you uninstall the app. (Please not that this will be true in Pebble SDK 2.0-BETA4 but was not before). Local storage exists on your mobile phone and will persist between app upgrades. It is important that you periodically review the data that your app is storing locally and remove any data that is unneeded or no longer relevant. This can be especially important when your internal app data structures change in an update to your app, as your new code will be expecting data formatted in a deprecated form. We strongly recommend that you version your data (adding a key `version` for example) so that a new version of your app can upgrade the format of your data and erase obsolete data.. ### Check for ACK/NACK responses It is imperative that you check for ACK/NACK responses to your last message between your Pebble and phone before initiating another message. Only one message can be sent at any one time reliably and if you try to send additional messages before ACK/NACK responses are received, you will start seeing failed messages and will have to resend them. [Pebble-faces](https://github.com/pebble-examples/pebble-faces/blob/master/src/js/pebble-js-app.js) is a good example that includes a `transferImageBytes()` function that sends an image to Pebble, waiting for an ACK after each packet. ### Familiarize yourself with the Geolocation API The W3C has [extensive documentation](http://www.w3.org/TR/geolocation-API/) on how to use the API, as well as best practices. Please be aware that support of this API is dependent on the underlying OS and results may vary wildly depending on the type of phone, OS version and environment of the user. There are situations where you may not get a response right away, you may get cached data, or you may never get a callback from your phone. We recommend that you: - Always pass an error handler to `getCurrentPosition()` and `watchPosition()` - and test the error scenarios! - Set up a timeout (with `setTimeout()`) when you make a geolocation request. Set it to 5 or 10 seconds and let the user know that something is wrong if the timeout fires before any of the callbacks. - Take advantage of the different options you can pass to customize the precision and freshness of data. - Test what happens if you disable location services on Pebble (on iOS, remove or refuse the authorization to get location, or turn off the iPhone's radio and Wifi ; on Android disable all the location services). - On your watch app, communicate to the user when the system is looking for geolocation information, and gracefully handle the situation when Geolocation information is not available. ### Double check your strings for malformed data Not all valid strings are valid in the context in which they're used. For instance, spaces in an URL are a bad idea and will throw exceptions. (It may also crash the iOS app in some earlier version of the SDK - Dont do it!) JSLint will not catch errors in quoted strings. ### Use the `ready` event The `ready` event is fired when the JS VM is loaded and ready to run. Do not start network request, try to read localstorage, communication with Pebble or perform similar operations before the `ready` event is fired! In practice this means that instead of writing: ``` console.log("My app has started - Doing stuff...");Pebble.showSimpleNotificationOnPebble("BadApp", "I am running!"); ``` You should do: ``` Pebble.addEventListener("ready", function() {console.log("My app has started - Doing stuff...");Pebble.showSimpleNotificationOnPebble("BadApp", "I am running!");}); ``` ### Do not use recurring timers such as setInterval() Your Pebble JavaScript can be killed at any time and you should not rely on it to provide long running services. In the future we may also limit the running time of your app and/or limit the use of intervals. Follow these guidelines to get the most out of long running apps: - Design your app so that it can be killed at any time. Save any important configuration or state in local storage when you get it - and reload it in the `ready` event. - If you need some function to run at regular intervals, set a timer on Pebble and send a message to the JavaScript. If the JavaScript is dead, the mobile app will re-initialize it and run the message handler. - Do not use `setInterval()`. And of course, do not use `setTimeout()` to simulate intervals. ### Identify Users using the PebbleKit JS account token PebbleKit JavaScript provides a unique account token that is associated with the Pebble account of the current user. The account token is a string that is guaranteed to be identical across devices that belong to the user, but is unique to your app and cannot be used to track users across applications. If the user is not logged in, the function will return an empty string (""). The account token can be accessed via the `Pebble.getAccountToken()` function call. Please note that PebbleKit JavaScript does not have access to the serial number of the Pebble connected. ### Determine a User's timezone using a simple JS script Pebble does not support timezones yet so when Pebble syncs the current time with your phone, it retrieves your local time and stores the local time as a GMT time (ex. 9AM PST -\> 9AM GMT), regardless of where you are located in the world. If you need to get the current timezone in your app, you can use this simple JavaScript code snippet: ``` function sendTimezoneToWatch() {// Get the number of seconds to add to convert localtime to utcvar offsetMinutes = new Date().getTimezoneOffset() \* 60;// Send it to the watchPebble.sendAppMessage({ timezoneOffset: offsetMinutes })} ``` You will need to assign a value to the `timezoneOffset` appKey in your `appinfo.json`: ``` { ... "appKeys": { "timezoneOffset": 42, }, ... } ``` And this is how you would use it on Pebble: ``` #define KEY_TIMEZONE_OFFSET 42 static void appmsg_in_received(DictionaryIterator *received, void *context) { Tuple *timezone_offset_tuple = dict_find(received, KEY_TIMEZONE_OFFSET); if (timezone_offset_tuple) { int32_t timezone_offset = timezone_offset_tuple->value->int32; // Calculate UTC time time_t local, utc; time(&local); utc = local + timezone_offset; } } ``` ### Use the source! We have a growing number of JS examples in the SDK and in our [`pebble-hacks`](https://github.com/pebble-hacks) GitHub account. Check them out! You can also [search for `Pebble.addEventListener` on GitHub](https://github.com/search?l=&q=Pebble.addEventListener+in%3Afile&ref=advsearch&type=Code) to find more open-source examples. ## Best Practices for the JS Configuration UI The new configuration UI allows you to display a webview on the phone to configure your Pebble watchface or app. Here are some specific recommendations to development of that UI. If you have not read it yet, you might want to take a look at our [Using PebbleKit JS for Configuration](https://developer.repebble.com/blog/2013/11/21/Using-PebbleKit-JS-Configuration.md) blog post. ### Immediately start loading your Web UI To avoid confusion, make sure UI elements are displayed immediately after the user clicks on settings. This means you should not do any http request or complex processing before calling `Pebble.openURL()` when you get the `showConfiguration` event: ``` Pebble.addEventListener("showConfiguration", function() {Pebble.openURL('http://www.example.com/mypebbleurl');}); ``` If the UI rendering is delayed, the user will get no feedbacks for their action (a press on the configure icon) and this would result in a poor user experience. ### Apply mobile & web best practices There is a lot of information on the web on how to build webpages that display nicely on mobile devices. iOS and Android both use WebKit based browsers which makes things easier, but it is safe to assume that you will never get the exact same look - and you will always need to test on both platforms. Even if you do not own both devices, you can use the built-in browser in the iOS or Android emulators to test your configuration webpage. Also consider using framework such as [JQuery Mobile](http://jquerymobile.com/) or [Sencha Touch](http://www.sencha.com/products/touch/) to make your life easier. They provide easy to use components and page templates. If you do not want to write any html, [setpebble.com](http://www.setpebble.com) is another great option that we have [discussed on the blog](https://developer.repebble.com/blog/2013/12/02/SetPebble-By-Matt-Clark.md) previously. ### Be prepared for the network to be unreachable during a UI configuration attempt UI configuration HTML is not stored locally on the phone and must be accessed from a hosted server. This introduces situations where a user's phone may not be connected to the network successfully, or your server may be unreachable, in which case the UI configuration screen will not load. In this scenario, a user will likely cancel the configuration load, which means a blank string is returned to your application `webviewclosed` event. Your watchapp should be designed to handle this such that the watchapp does not erase all settings or crash. ### Display existing settings When displaying your configuration UI, remember to pass existing settings to the webview and use them to pre-populate the UI! ### Name your configuration submission button "Save" If you want to ensure that the settings a user has entered are saved correctly, make sure you call your button _Save_ so users understand that it is required to save data. If your configuration page is long, consider putting the _Save_ button at both the top and bottom of the page. If the user clicks the Exit button (X), a blank string will be passed back to your application. You should be prepared to handle a blank string should a user decide to cancel the configuration process. ### Prompt users to complete app configuration in your Pebble app Users may not be aware that they need to complete a configuration for your watchapp. It is best to advise users in the watchface or watchapp to go to their phone and configure your app before proceeding. Use persistent storage or local storage to save a token indicating the user has completed configuration, and consider putting a version string in, to allow you to track which version of your configuration webpage the user has accessed (to assist future upgrades of your app). ## Questions? More tips? Have more questions? Or want to share more tips? Please post them in the comments below! * * * --- # All Aboard the Pebble appstore Source: https://developer.repebble.com/blog/2013/12/18/All-Aboard-the-Pebble-app-store/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 18 December 2013 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2013/12/18/All-Aboard-the-Pebble-app-store.md#comments) We are extremely excited to announce the Pebble appstore! Many of you, as developers, have asked for it and we are happy to announce that it will go live with Pebble SDK 2.0 early next year. Pebble appstore will be included in the official Pebble smartphone application and will be the first place where Pebble users look for Pebble apps. For developers, the Pebble appstore is the best way to promote and distribute your Pebble applications to users. ![Pebble appstore devices](https://developer.repebble.com/assets/images/blog/pebble-appstore-devices.png) The Pebble appstore includes watchfaces and 6 app categories for Daily, Remotes, Sports & Fitness, Notifications, Tools & Utilities and Games. In each category, we will feature a selection of the best as well as new apps. ![Pebble appstore categories](https://developer.repebble.com/assets/images/blog/pebble_appstore_categories.png) If your Pebble app requires a companion iOS or Android app, you can also include links to Google Play or the Apple App Store to help users find your app. If you have built an iOS or Android app that leverages the Pebble Sports API and does not require a specific Pebble app to be installed on Pebble, you can also add an entry in the Pebble appstore that links to your native app. [As you know](https://developer.repebble.com/blog/2013/11/06/A-New-Era-of-Apps.md), Pebble 1.x apps will not run on the 2.0 firmware. To facilitate the migration for users, we will automatically upgrade existing Pebble apps on a user’s Pebble when upgrading the new firmware. In order to take advantage of this, you will need to have your new app be ported to the 2.0 SDK, submit your 2.0 app to the appstore and register your 1.x UUID in your 2.0 appstore profile. Submitting an application on the Pebble appstore is free and open to everyone. To get started: 1. Read the [_Appstore Publishing_](https://developer.repebble.com/guides/appstore-publishing.md) guides. 2. Join the [developer dashboard](http://dev-portal.getpebble.com/). ![An application on the Pebble Developer Dashboard](https://developer.repebble.com/assets/images/blog/pebble_appstore_webportal.png) More information and answers to common questions after the break! ### What is the developer dashboard? The developer dashboard is the web application that developers use to publish an iOS or an Android application to the Pebble appstore. The URL for the developer dashboard is [https://dev-portal.getpebble.com](https://dev-portal.getpebble.com). Access to the developer dashboard is free. ### Will you validate / approve applications? All applications need to conform to the Pebble appstore [Developer Agreement](https://dev-portal.getpebble.com/developer/agreement). We will not pre-validate apps but reserve the right to take down any app that does not conform. ### Does the appstore support paid applications? No, not at this time. You can however submit applications that work with an iOS or Android companion app and charge for the mobile apps in the Apple App Store or Google Play! Pebble appstore allows you to add convenient links to your Companion mobile app. ### How do I get my application in one of the featured collections? Submit your application on the store and make sure it is “published”. You can then [contact the devsupport team](https://developer.repebble.com/contact.md) to discuss featuring your app. ### How will JavaScript apps be distributed to iOS users? The Pebble iOS application cannot execute code downloaded from the Internet so we will package the JavaScript code of all Pebble appstore apps and include them in the official Pebble iOS app. ### What about mypebblefaces.com? We are immensely grateful for the awesome work done by Jason Smyth of MyPebbleFaces.com. His contribution to the community is invaluable, as well as other individuals that have contributed significantly on the app discovery and installation front. We want to continue to encourage these efforts. Third party app stores will still work for 2.0 users and we will not prevent or block installation of applications by 3rd party websites. We hope to serve the needs of the majority of our users and look forward to new ideas being implemented by the community in third party services. To show our support for the particular effort put into MyPebbleFaces.com, Pebble made a donation to The Neuromuscular Center, the association that supported the cost and energy of running MyPebbleFaces.com. * * * --- # Open Lab For Pebble Developers Source: https://developer.repebble.com/blog/2013/12/17/Open-Lab-For-Pebble-Developers/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 17 December 2013 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/12/17/Open-Lab-For-Pebble-Developers.md#comments) Hi Pebblers! We're really looking forward to our next open lab that we're hosting at Pebble HQ this Friday. If you haven't had a chance yet to join an open lab (in-person) or a virtual lab (online), this is your chance! This is a great opportunity for you to hack on your latest projects and receive help from our developer evangelists, Thomas and Cherie. ##### DATE Friday, December 20, 2013 ##### TIME 3PM-6PM PST ##### PLACE Pebble HQ Please RSVP for this event here: [![Eventbrite - Open Lab for Pebble Developers](http://www.eventbrite.com/custombutton?eid=9635216191)](http://www.eventbrite.com/e/open-lab-for-pebble-developers-registration-9869644372?aff=BlogPost20131217) Still haven't had a chance to try out the SDK? [Download](https://developer.repebble.com/sdk.md) the SDK, check out the [documentation](https://developer.repebble.com/guides.md), and [get hacking](https://developer.repebble.com/getting-started/)! * * * --- # Sdk Beta3 Now Available Source: https://developer.repebble.com/blog/2013/12/12/SDK-BETA3-Now-Available/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 12 December 2013 - cherie & thomas - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2013/12/12/SDK-BETA3-Now-Available.md#comments) This holiday season is off to a great start and the Pebble community is getting larger every day! We wanted to take the time today to thank you all for your ongoing support and feedback on the Pebble SDK, and more importantly, for sticking with us through all of the bugs and issues. You are the most amazing people we could have to work with -- thanks!!! Just a quick reminder: Pebble 2.0 will be released to all Pebble users soon. We announced the 2.0 SDK back in November, but you can still view the original [presentation](http://www.youtube.com/watch?v=uoVX2ibCFXw&feature=youtu.be) ([or just the slides](http://www.slideshare.net/pebbledev/announcing-pebble-sdk-20)). Pebble 1.x applications are not compatible with this new major release. You will need to upgrade your apps to prepare for the 2.0 launch. However, as you go through the process of porting your apps, we're here to help you! Please don't hesitate to [contact us](https://developer.repebble.com/contact.md) if you have any questions and we'd love to hear about when you complete your 2.0 apps. Please send us an email when you're done so we can help you share your Pebble app with Pebblers everywhere! Today we are releasing an update to the 2.0 beta that fixes a lot of bugs and introduces a few new features. The full [release notes](https://developer.repebble.com/sdk/changelogs/2.0-BETA3.md) are available on our website, but the key changes in this version are: \* The iOS app fixes a number of data logging issues, along with the developer connection bug. \* The Android app fixes a large number of JS-related bugs. \* The Android app fixes a bug where all messages sent to Android would be automatically acknowledged. Your application should acknowledge its own app messages. \* Some new user features in the firmware, such as notification settings (do not disturb), and better alarms \* Lots of small UI and stability fixes in Pebble #### [Download & Install the Latest SDK](https://developer.repebble.com/sdk/download/) If you are an iOS developer and you haven't already downloaded the beta iOS mobile app, please complete [this form](https://docs.google.com/a/pulse-dev.net/forms/d/14r3MHPsdH5ha-BCkfuquQuAKuQSEJLmxm--XXpBA8mg/viewform) to get access. If you already completed the form, you will be receiving an update automatically, within the next 24 hours. #### Help us continue to improve Pebble 2.0! We opened several threads on the forums in November to help you report bugs or issues. Please continue to report anything suspicious in these specific threads to guarantee that we see them quickly. \* [Feedback on Pebble 2.0 User Experience](https://forums.getpebble.com/discussion/8533/pebble-2-0-beta-feedback-on-user-experience) \* [Feedback on Pebble 2.0 JavaScript APIs](https://forums.getpebble.com/discussion/8531/pebble-2-0-beta-feedbacks-on-pebblekit-javascript) \* [Feedback on Pebble 2.0 DataLogging API](https://forums.getpebble.com/discussion/8532/pebble-2-0-beta-feedback-on-datalogging) \* [Feedback on Pebble 2.0 AppMessage](https://forums.getpebble.com/discussion/8534/pebble-2-0-beta-feedback-on-appmessage) #### Still need a Pebble for development? We're running the [#ifihadpebble challenge](https://developer.repebble.com/blog/2013/11/20/Win-A-Pebble.md) until December 25th, so submit your app ideas for a chance to earn a free Pebble! We will also be announcing a number of developer challenges in the near future -- stay tuned to the [developer blog](https://developer.repebble.com/blog) for the latest news! #### Open/Virtual Labs for Pebble Developers We've started hosting our [open (in-person) labs](https://developer.repebble.com/blog/2013/11/03/Developer-News-and-Reddit-and-Open-Lab.md) and [virtual (online) labs](https://developer.repebble.com/blog/2013/12/03/Virtual-Lab-For-Pebble-Developers.md) and are really excited about the feedback we've been receiving. Our plan is to continue to host these weekly -- check the [developer blog](https://developer.repebble.com/blog) for announcements of dates and other details. #### CES & AT&T Developer Summit Hackathon If you're going to [CES](http://www.cesweb.org/) in January 2014, please do get in touch! We'd love to say hi and get you some Pebble goodies. We'll also be working hard the weekend before CES at the [AT&T Developer Summit Hackathon](https://devsummit.att.com/); stop by for a chance to hack on the Pebble and to win some amazing wearables prizes! #### Come work with us at Pebble! We're hiring like crazy right now and we'd love to have you come join us! Pebble is a fantastic place to work and there are countless opportunities for you to grow within the organization. Check out our [jobs page](https://www.getpebble.com/pages/jobs) for more details. * * * --- # Virtual Lab For Pebble Developers Source: https://developer.repebble.com/blog/2013/12/03/Virtual-Lab-For-Pebble-Developers/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 December 2013 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/12/03/Virtual-Lab-For-Pebble-Developers.md#comments) Hi Pebblers! We had a blast at the first open lab that we hosting at Pebble HQ last Friday. We know many of you were interested in joining, but perhaps you don't live in the area, or you just weren't able to make it; to help with this, we're now announcing our first Virtual Lab for Pebble Developers! This is a great opportunity for you to hack on your latest projects and receive help from our developer evangelists, Thomas and Cherie, in an online setting. ##### DATE Friday, December 13, 2013 ##### TIME 3PM-6PM PST ##### PLACE irc.freenode.net #pebble (we'll move into a Hangout if we find we need to) We would greatly appreciate an RSVP, just so we know how many developers we'll be working with. To RSVP: [![Eventbrite - Virtual Lab for Pebble Developers](http://www.eventbrite.com/custombutton?eid=9635216191)](http://www.eventbrite.com/event/9635216191?ref=ebtnebregn) Still haven't had a chance to try out the SDK? [Download](https://developer.repebble.com/sdk.md) the SDK, check out the [documentation](https://developer.repebble.com/guides.md), and [get hacking](https://developer.repebble.com/getting-started/)! * * * --- # Setpebble By Matt Clark Source: https://developer.repebble.com/blog/2013/12/02/SetPebble-By-Matt-Clark/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 02 December 2013 - Matt Clark (SetPebble) - [At the Pub](https://developer.repebble.com/blog/tags/at-the-pub/) - [Comments](https://developer.repebble.com/blog/2013/12/02/SetPebble-By-Matt-Clark.md#comments) The last Developer Blog post introduced the JS configuration utility, and included some screenshots from [SetPebble.com](http://setpebble.com). Thomas and Cherie of Pebble asked me to guest blog about SetPebble and JS configuration, so here goes. When I started creating Pebble apps, developers were publishing 2, 4, even 8 versions of their apps to cover different flavors such as screen inversion, text size, etc. Soon after publishing my Fair Weather app, I received requests to switch the units to Celsius, wind speed to meters, etc. Not wanting to create umpteen versions, I learned how to uniquely identify Pebbles when they accessed the Internet. Therefore when my server transmits weather information to the Pebble watch, it also delivers customizable settings such as the temperature units. The website I created ([SetPebble.com](http://setpebble.com)) is designed to store customizable settings for Pebble users. Users can enter their serial number, and then they can choose their customized settings. For Pebble SDK 2.0, Pebble apps can now store settings in persistent storage within the Pebble smart phone app. On the Pebble smart phone app, configurable apps now display a gear (Android) or round button (iOS) in the app list. Clicking on this button launches a web view which can be used to select custom settings. So why is [SetPebble.com](http://setpebble.com) needed for SDK 2.0 if settings can be stored locally? There are many reasons, and the first of all is that the settings are safe in the cloud and survive the installing and deinstalling and app crashes. Users can control their settings from any web browser, and even password-protect their settings. Developers get a fast and convenient method for adding custom settings to their Pebble apps and watchfaces, plus have summary reports regarding usage. I can add custom settings to an app in a matter of minutes using the setup screens at [SetPebble.com](http://setpebble.com). One cool feature is being able to communicate with your Pebble users: a setting may be classified as “hidden” and I use this for setting a version number. If one of my SetPebble-enabled apps receives a setting with a version number higher than the current version, then an “update available” message is displayed on the screen. I have opened up [SetPebble.com](http://setpebble.com) to any developer wishing to easily add custom settings for their app or watch face. For developers, the benefit is convenience and usage tracking, since SetPebble has a dashboard per app showing usage. For users they receive a consistent and quality experience for configuring their Pebble, and they also have the option of configuring their options through any web browser if they choose to enter their serial number. One caveat to developers is that the JS configuration utility is still in beta. All my SDK 2.0 apps are working great in iOS, but the web views are not always launching in Android. A future SDK 2.0 beta release should solve this issue. Why did I create [SetPebble.com](http://setpebble.com) and open up this service to other developers? The short answer is passion. Developing for the Pebble is fun and challenging, and the positive user feedback is satisfying. And Pebble development is turning into a family affair - my 11-year-old son thought of the idea for [Bot Buddy](http://setpebble.com/info?app=24) and helped with the design. Please refer to these resources for examples of using the JS configuration with [SetPebble.com](http://setpebble.com): SetPebble Developer page: [http://setpebble.com/developers](http://setpebble.com/developers) Sample SetPebble-enabled Simplicity Watchface: [http://github.com/SetPebble/Simplicity2](http://github.com/SetPebble/Simplicity2) Readme file with Screenshots: [https://github.com/SetPebble/Simplicity2/blob/master/README.md](https://github.com/SetPebble/Simplicity2/blob/master/README.md) Happy Pebbling! Matt Clark * * * --- # Using Pebblekit Js Configuration Source: https://developer.repebble.com/blog/2013/11/21/Using-PebbleKit-JS-Configuration/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 21 November 2013 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/), [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2013/11/21/Using-PebbleKit-JS-Configuration.md#comments) One of the most useful additions to the Pebble SDK in the [2.0 beta release](https://developer.repebble.com/sdk.md) is the introduction of PebbleKit JS and the ability to use a webview on your phone to configure your watch app. Since we've been seeing a good number of questions on this new feature, we figured we'd talk about it a little more. ### What is PebbleKit JS? PebbleKit JS is a new library in the Pebble SDK that allows your watch to communicate with the rest of the world/internet from within the Pebble mobile app itself. This means you don't have to have Android or iOS-specific knowledge in order to interact with web services or to create configuration interfaces on your mobile phone. ### What is the JS configuration utility? The JS configuration utility allows you to create a configuration interface for your users to access on their mobile phones. You can use this to collect user information and credentials, or to create a place where users can adjust settings in your app. For example, in 1.x, a lot of duplicate apps were created to handle variations in languages, different watchface appearances, and time formatting. With the new JS config utility, you can setup a webview for your users to configure their preferred language, their preferred date format, and any variations you want to allow in the actual appearance of your watchface. ### How do I use it? Using the JS config utility is very easy! To get started, read [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md). ![](https://developer.repebble.com/assets/images/blog/jsconfigure.png) Thomas also created a cool demo of the configuration utility on [GitHub](https://github.com/pebble-hacks/js-configure-demo). ### What now? Not sure how to implement a JS configuration view? You can also try out the nifty [SetPebble](http://setpebble.com) site, which auto-generates your JS webview for you based off of values you enter on the SetPebble site. The instructions on how to do this can be found [here](http://setpebble.com/developers) Questions? Have an app that uses the JS configuration utility? [Contact us!](https://developer.repebble.com/contact.md) * * * --- # Win A Pebble Source: https://developer.repebble.com/blog/2013/11/20/Win-A-Pebble/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 20 November 2013 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/11/20/Win-A-Pebble.md#comments) We recently introduced our [Pebble SDK 2.0 beta](https://developer.repebble.com/sdk.md) and we're excited to now also announce a new way for you to earn a Pebble to develop your 2.0 apps on! ![](https://developer.repebble.com/assets/images/blog/pebbles.png) ### #ifihadpebble Challenge Starting today (Wednesday, November 20th) and running until Christmas (December 25th), we'll be selecting one developer a day to win a free Pebble (worldwide shipping), to help you build your #ifihadpebble app ideas. To enter, simply: 1. Follow the [@pebbledev](https://www.twitter.com/pebbledev) Twitter account (this is so we can DM you later for your details) 2. Tweet what app(s) you would build if you won a Pebble and include the #ifihadpebble hashtag 3. Get your friends to help spread the word 4. Check [@pebbledev](https://www.twitter.com/pebbledev) every morning to see if you won! There are a few basic rules that you need to adhere to: 1. You can only enter once per day (12:00AM PST - 11:59PM PST) 2. Your idea must fit into a single tweet 3. Your idea must make use of a new SDK 2.0 feature 4. You must tweet from a Twitter account that has a reasonable amount of history/activity 5. All decisions by Pebble are final 6. If you try to game the system in any way, we will ban you for the remainder of the contest Questions? [Contact us!](https://developer.repebble.com/contact.md) Good luck and #makeawesomehappen Cherie & Thomas * * * --- # Top Pebble Sdk 2.0 Apps Source: https://developer.repebble.com/blog/2013/11/07/Top-Pebble-SDK-2.0-Apps/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 07 November 2013 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/11/07/Top-Pebble-SDK-2.0-Apps.md#comments) Yesterday, we announced the beta release of our 2.0 SDK and got overwhelmingly helpful feedback from all of you. Today, we are equally excited to announce that your favorite apps are now also available on v2.0, ready to download! We contacted some of the developers to get their feedback on the new SDK, along with some tips and tricks for those of you that are just starting out with the new Pebble SDK. Apps: [7-Minute Workout](#seven-minute-workout) | [WatchNote](#watchnote) Apps (iOS Only): [Smartwatch+](#smartwatch+-and-smartstatus) | [Smartwatch Pro for iOS](#smartwatch-pro-for-ios) | [SmartStatus](#smartwatch+-and-smartstatus) | [PebbleCam](#pebblecam) Apps (Android Only): [Canvas for Pebble](#canvas-for-pebble) | [Glance for Pebble](#glance-for-pebble) | [Music Boss for Pebble](#music-boss-for-pebble) | [Pebble Notifier](#pebble-notifier) | [Pebble Tasker](#pebble-tasker) Watchfaces: [Dotz](#dotz-watchface-fancy-watchface-maurice-watchface-modern-watchface) | [Fancy](#dotz-watchface-fancy-watchface-maurice-watchface-modern-watchface) | [Maurice](#dotz-watchface-fancy-watchface-maurice-watchface-modern-watchface) | [Modern](#dotz-watchface-fancy-watchface-maurice-watchface-modern-watchface) | [Noms](#noms) | [Revolution](#revolution) * * * #### Smartwatch Pro for iOS [Max Bäumle](http://appstore.com/smartwatchproforpebble) A companion application for your phone that provides useful additional functionality with your Pebble watch including displaying upcoming calendar events, your Twitter timeline, volume control and more. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartwatchpro.jpeg)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartwatchpro.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartwatchpro.pbw) ###### Current/Future 2.0 Features: I am planning to implement Event Services and persistent storage for various parts of the application. ###### Time to Port: 2 days for migrating from 1.x to 2.0 without adding new features. ###### Thoughts on SDK: Overall, the new SDK feels much more mature than the 1.x preview. Feature-wise, dynamic memory allocation is definitely among my personal favorites since it opens up the possibility to implement more innovative features in the future. Improved SDK installation process, rewritten quick start guides and sample code will make it a lot easier for starters to get familiar with development for the Pebble watch platform. ###### Tips: Refer to the 2.0 migration guide if you are converting an existing project. The `pebble` command line tool will come in handy for that as well. Do not hesitate to [contact Pebble developer support](https://developer.repebble.com/contact.md) or Twitter (@PebbleDev) if you are getting stuck. I can assure you they will do their best to help you out with any request * * * #### Seven-Minute Workout [Alex Kennberg](http://kennberg.com/7min) Personal guide through the high-intensity 7 minute workout as was featured in the New York Times. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/7-minscreen.png)](http://kennberg.com/7min/) [Download](http://kennberg.com/7min/) ###### Time to Port: 1 hour ###### Thoughts on SDK: Great documentation, polished APIs, and solid foundation for more features. * * * #### Music Boss for Pebble [Reboot's Ramblings](http://musicboss.ca) Adjust volume while listening to music, change music apps and even launch your current music app right from your Pebble. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/musicbossscreen.png)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/musicboss.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/musicboss.pbw) ###### Current/Future 2.0 Features: I am planning on using tap events for some new features, but wanted to make sure users were able to transition to the new OS without issues and still be able to use the Music Boss they know and love ###### Thoughts on SDK: The Pebble team is really trying to make coding for the Pebble Smart watch easier for developers. They are cleaning up certain design architectures and streamlining the app build and install tools. It's never been better to start coding for the Pebble. ###### Tips: If you are stuck, check the forums, if you are still stuck, reach out to other Pebble developers. My experience has been that we (and they) are very willing to help fellow devs. * * * #### Pebble Tasker [Hernan Cosi - KodeK Apps](https://play.google.com/store/apps/details?id=com.kodek.pebbletasker&hl=en) Allows you to control your Android phone through Tasker directly from your watch. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/taskerscreen.png)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/tasker.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/tasker.pbw) ###### Current/Future 2.0 Features: None yet, but there are a few new features planned. ###### Time to Port: Less than a weekend. Could be done in just a few hours now that I'm familiar with the new API. ###### Thoughts on SDK: By far my favorite feature is the new 'pebble' tool and all its new functionality. Run 'pebble build && pebble install' to quickly deploy your app's latest changes. ###### Tips: 'pebble --debug logs' will be incredibly useful if you're working with AppMessage packets. * * * #### Smartwatch+ and SmartStatus [Robert Hesse](http://smartwatchplusios.appspot.com) Companion app that enhances the Pebble experience for iOS users | Watch face for Smartwatch+ [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartwatchplus.gif)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartwatchplus.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartwatchplus.pbw) [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartstatus.gif)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartstatus.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/smartstatus.pbw) ###### Current/Future 2.0 Features: BluetoothConnectionService, malloc, Persistent Storage ###### Time to Port: ~8h ###### Thoughts on SDK: malloc allows to pack much more capable apps into the app size limit ###### Tips: Use malloc! * * * #### WatchNote [Leistner Apps](http://www.facebook.com/Watchnote) Create, edit and send multiple notes from your smartphone to your wrist. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/watchnotescreen.png)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/watchnote.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/watchnote.pbw) ###### Current/Future 2.0 Features: At the moment we don't use 2.0 features ###### Time to Port: about 8 to 10 hours ###### Thoughts on SDK: I like the massive amount of new possibilities. * * * #### Glance for Pebble [Finebyte](http://www.finebyte.co.uk) Glance gives fast access to the information you need and enables quick responses to messages right from your Pebble. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/glance.gif)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/glance.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/glance.pbw) ###### Current/Future 2.0 Features: Bluetooth connection monitoring and Battery status. Tap gestures are coming! ###### Time to Port: Just over a week ###### Thoughts on SDK: Getting more access to the Pebble hardware - both the basics like BT and battery, and some new fun toys like the accelerometer. ###### Tips: Be careful with your memory allocation - remember you need to check your \_creates and allocations were successful! * * * #### Canvas for Pebble [Steve Penna](http://pebblecanvas.blogspot.co.uk/) Design and share dynamic Pebble watch-faces on your Android phone Coming Soon ###### Current/Future 2.0 Features: Persistent storage, dynamic memory allocation, watch state (bluetooth connection / battery), utility functions (e.g. atoi), proper debugging via websockets ###### Time to Port: A few days to do a basic port, many weeks to add cool new features based on the new capabilities (including updates to the Android app) ###### Thoughts on SDK: Dynamic memory allocation and persistent storage - I can't decide between the two but they are the most liberating features in the 2.0 SDK! The new debugging features are incredibly useful (e.g. pebble install, pebble logs), in combination with the app\_log functionality. ###### Tips: Use debug liberally and #IFDEF it for when you get everything working ;) * * * #### Pebble Notifier [Dattas Moonchaser](https://github.com/dattas/pebble-notifier) Sends any notification from your Android phone to your Pebble. ![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/notifierscreen.png) ###### Thoughts on SDK: I am quite excited to see persistent storage! * * * #### PebbleCam [Heiko Behrens & Marcel Jackwerth](http://PebbleCam.com) Stream your iPhone's camera to your Pebble and take high quality pictures remotely. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/pebblecamscreen.png)](http://pebblecam.com/pebble-2-0-firmware) [Download](http://pebblecam.com/pebble-2-0-firmware) ###### Current/Future 2.0 Featues: Better tool chain to automatically build the Pebble app whenever the iOS app is being compiled. ###### Time to Port: Less than three hours. ###### Thoughts on SDK: The Pebble SDK 2.x is far more convenient and faster to develop with - simpler setup, hassle-free deployment and more comfortable testing. ###### Tips: Have a look at the JavaScript Framework, that's a huge opportunity - no need for an additional mobile app, always-running app that can retrieve and crunch data from the web, a simplified installation and configuration for Pebble's end users. * * * #### Dotz Watchface | Fancy Watchface | Maurice Watchface | Modern Watchface Łukasz Zalewski Animated watchface which wont eat your battery | Fancy look analogue watchface | Pointy look analogue watchface | Modern look analogue watchface [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/dotz.gif)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/dotz.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/dotz.pbw) [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/fancy.png)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/fancy.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/fancy.pbw) [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/maurice.png)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/maurice.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/maurice.pbw) [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/modern.png)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/modern.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/modern.pbw) ###### Current/Future 2.0 Features: Will be coming in the future ###### Time to Port: 4 hours | 1 hour | 1 hour | 1 hour ###### Thoughts on SDK: Cleaning up workspace for pebble projects, my pebble projects now have much nicer structure and code is easier to read. ###### Tips: Remember to initialize the accelerometer service handler properly even if you're using only the accel\_service\_peek, or your application will keep on crashing. * * * #### Revolution [Douwe Maan](http://www.douwemaan.com) Beautiful watchface with big, clear digits and smooth animations. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/revolution.png)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/revolution.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/revolution.pbw) ###### Time to Port: 2 hours ###### Thoughts on SDK: The new JavaScript and persistence frameworks open a world of possibilities for new Pebble apps * * * #### Noms [Fazli Sapuan](https://github.com/fuzzie360/pebble-noms) Watch face "noms" the time every minute. [![](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/noms.gif)](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/noms.pbw) [Download](http://pebble-static.s3.amazonaws.com/watchfaces/beta20/apps/noms.pbw) ###### Time to Port: 15 mins ###### Thoughts on SDK; The workflow is more streamlined and API is saner than ever before. * * * Stay up-to-date with the latest Pebble developer news with [RSS](https://developer.repebble.com/atom.xml)! * * * --- # A New Era Of Apps Source: https://developer.repebble.com/blog/2013/11/06/A-New-Era-of-Apps/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 06 November 2013 - team pebble - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2013/11/06/A-New-Era-of-Apps.md#comments) Hi Pebble Developers! Today we are excited to announce Pebble SDK - Version 2.0. This new release adds tons of new features and makes it even easier for developers to build awesome apps and watchfaces for Pebble. Since we launched the first version of our SDK in April, we’ve been getting feedback and suggestions from developers around the world, and we’ve been working hard to incorporate your suggestions in this new release. ### Some of the most notable changes in this release are: #### New Tools! `pebble build && pebble install --phone ` is all it takes now to build and install Pebble watch apps, and that's just the beginning! Developers now also have easy access to comprehensive logs with 'pebble logs' as well as useful feedback from the watch on app memory usage levels #### PebbleKit Javascript Pebble SDK now provides a library that allows you to develop cross-platform, web-aware applications without a specific Android/iOS companion app. Developers with limited mobile experience can now build apps that work with both iOS and Android seamlessly, simply by bundling Javascript with watchapps. #### Persistent Storage Apps can now store or cache data in between application sessions. Want to keep track of those high scores, or improve the loading time of your web-aware watch app? Done. #### Dynamic Memory Our good friends malloc() and free() are now enabled, allowing you to manage your own memory and create apps that are larger and more fully-featured. You can say good-bye to those statically allocated structures from 1.0, and say hello to beautiful dynamic arrays and linked lists! #### Data Logging The new data logging API in SDK 2.0 introduces a simplified messaging system that ensures reliable one-way message transmission from the watch to an iOS/Android device. Developers can now take a "fire and forget" approach to messaging and focus on app development, leaving connection monitoring and buffer management to Pebble. #### Accelerometer Access Pebble SDK now supports access to raw 3-axis data from the built-in watch accelerometer. Use this data to track Pebblers' physical activity, to control software on your phone or computer, or to detect gestures. Additional details can be found in [the release notes](https://developer.repebble.com/sdk/changelogs/2.0-BETA1.md) We're proud to announce that more and more developers are creating apps and solutions for Pebble. We're excited to be working with GoPro, Yelp, Foursquare, and iControl to take advantage of the new APIs in Pebble SDK 2.0. ### Ready to dive in? ##### [Download the new SDK](https://developer.repebble.com/sdk/download/) ##### [Get started with the guides](https://developer.repebble.com/guides.md) ##### [Browse the APIs](https://developer.repebble.com/docs.md) ##### [Find answers to your questions](https://forums.getpebble.com/categories/developer-discussion) Not a developer? Don’t worry, this is a beta release that anyone can opt to download and try. We’ll also be pushing out the release as an over-the-air update to all users later this year. We hope you will enjoy these new tools that we have added to the SDK, as well as the improvements we've made to notifications by [incorporating ANCS and BTLE on iOS and Android devices](https://blog.getpebble.com/2013/11/06/introducing-ios-7-notifications-and-pebble-sdk-2-0/). As always, if you have any questions on how to use the SDK, please [contact us](https://developer.repebble.com/contact.md). Missed the livestream? You can still view the [recording](https://www.getpebble.com/live) or peruse the [slides](http://www.slideshare.net/CherieWilliams1/pebble-sdk2betalaunch) Stay up-to-date with the latest Pebble developer news with [RSS](https://developer.repebble.com/atom.xml)! * * * --- # Developer News And Reddit And Open Lab Source: https://developer.repebble.com/blog/2013/11/03/Developer-News-and-Reddit-and-Open-Lab/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 November 2013 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/11/03/Developer-News-and-Reddit-and-Open-Lab.md#comments) Hi Pebblers! It's no secret that we've been hard at work developing better software for Pebble and this Wednesday, November 6th, we'll be making a very important announcement. You're invited to join our livestream event at 10AM PST to hear what awesome new things we've been working on to make Pebble a more robust and feature-rich platform. To tune in, visit [https://www.getpebble.com](https://www.getpebble.com) on Wednesday morning after 9AM PST to obtain a link for the livestream. After the live broadcast hosted by Eric and our very own [Thomas](https://www.twitter.com/sarfata), we will be leading a Reddit AMA discussion at noon (12PM PST) to answer any lingering questions you may have. ### Introducing Open Labs! Over the last couple of weeks, we've been working on ways to better serve the Pebble developer community as an evangelist team, and we're happy to announce that we are hosting our very first Open Lab for Pebble Developers on Friday, December 6th. We're excited to invite you to come visit us at Pebble HQ for 3 hours of intense Pebble hacking and troubleshooting. For this first event, we want to make sure we can work with everyone that attends, so we've limited attendance to 10 developers; however, if you're not able to make it for whatever reason, have no fear! This is going to be a weekly event (we just love you guys too much to do this only once!) [![Eventbrite - Open Lab for Pebble Developers](https://www.eventbrite.com/custombutton?eid=9167392919)](http://www.eventbrite.com/event/9167392919/BlogPost20131103) Still haven't had a chance to try out the SDK? [Download](https://developer.repebble.com/sdk.md) the SDK, check out the [documentation](https://developer.repebble.com/guides.md), and [get hacking](https://developer.repebble.com/getting-started/)! * * * --- # Meet The Pebble Developer Evangelists Source: https://developer.repebble.com/blog/2013/10/29/Meet-the-Pebble-Developer-Evangelists/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 29 October 2013 - [Cherie Williams](https://developer.repebble.com/blog/authors/cherie/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/) - [Comments](https://developer.repebble.com/blog/2013/10/29/Meet-the-Pebble-Developer-Evangelists.md#comments) Hi Pebblers! It’s been awhile since our last update to the developer’s blog, but we have some exciting news to share! As many of you have probably noticed, the developer evangelism team here at Pebble has been growing over the last few months and we’re excited to introduce our newest additions! ![](https://developer.repebble.com/assets/images/blog/cherie_and_thomas.jpg) [Thomas Sarlandie](http://www.sarfata.org/) joined Pebble in early July, traveling all the way from Senegal to be with us. Thomas grew up in France with a passion for computers and the Internet and prior to joining Pebble, co-founded and was CTO of Backelite, a leading french mobile development agency. He enjoys [writing books](http://www.amazon.fr/Programmation-Conception-publication-dapplications-iPhone/dp/2212136390), building robots, long distance amateur radio, hacking kernel drivers and doing security testing. When not in front of a computer, he sails, takes pictures and tries to understand Yoga. Some of you already met him at the August Pebble Developer Retreat, but if you didn’t, say hello to [@sarfata](https://twitter.com/sarfata)! Cherie Williams joined Pebble in mid October. Her first exposure to programming happened at an early age when her dad handed her [Basic Computer Adventures](http://www.atariarchives.org/bca/). She went on to study Electrical Engineering in college, where she founded and led the UC Davis team that participated in the [DARPA Grand Challenge](http://en.wikipedia.org/wiki/DARPA_Grand_Challenge). Her recent professional life has included lots of Ruby on Rails, working at HP Autonomy, and co-founding a startup. Cherie enjoys flying (PPL/IR), sailing, rock climbing, skiing, cycling and otherwise getting into as much trouble as possible. Say hello to [@cherie\_williams](https://twitter.com/cherie_williams)! The next few months promise to be exciting for both Pebblers and Pebble Developers alike and we hope you stay tuned to our developer blog (which we will be updating much more frequently) for the latest news. We’d love to hear from you and help you out with all of your amazing projects, ideas, questions, issues, suggestions, and funny stories. [Contact us](https://developer.repebble.com/contact.md)! #makeawesomehappen * * * --- # Two Way Communication With Appmessage Source: https://developer.repebble.com/blog/2013/09/27/Two-Way-Communication-With-AppMessage/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 27 September 2013 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/09/27/Two-Way-Communication-With-AppMessage.md#comments) On september 25th, the Pebble User Group met at the Hacker Dojo in Mountain View to discuss two-way communication with Pebble SDK. The slides are now available for everyone to enjoy - just after the break! [EMBED]([http://www.slideshare.net/slideshow/embed\_code/26625888](http://www.slideshare.net/slideshow/embed_code/26625888)) Please do share your questions and feedbacks in the comments below. * * * --- # Pebble Developer Retreat Source: https://developer.repebble.com/blog/2013/09/26/pebble-developer-Retreat/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 26 September 2013 - tom@getpebble.com - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/09/26/pebble-developer-Retreat.md#comments) If you were there, you’re probably still talking about it, right? If not, you missed a great opportunity, which you’ll want to take advantage of next time. Create a great Pebble app and be there! I’m talking about the recent Pebble Developer Retreat, which took place at Pebble HQ in Palo Alto, California, during the waning days of summer on the eve of Labor Day weekend, August 29th to September 2nd. What an amazing event! Way cool, truly awesome -- all that and more! More than one hundred Pebble software developers, many of whom had already created really cool Pebble watchface and watchapps, converged in a series of presentations, hands-on coding workshops, discussions, and lots of fun, food and fellowship at the Retreat. ![alt text](https://developer.repebble.com/assets/images/blog/pebble-dev-retreat-02.png) The event was hardly a “retreat” by any stretch of the imagination or in the conventional sense of the word, but rather, a giant leap forward for Pebble developers moving their code in preparation for the next release of the Pebble SDK. Ultimately, of course, Pebble users will get to enjoy the benefits and rewards of this event, as Pebble developers from all over the world create and build the next generation of great Pebble software. Eric Migicovsky, Pebble CEO and Founder, kicked off the event on Thursday morning, welcoming developers who crossed oceans to come for the event. Eric emphasized how important developers were to building and expanding the Pebble ecosystem with the next generation of Pebble apps and how dedicated Pebble is to supporting this community in every way possible. ![alt text](https://developer.repebble.com/assets/images/blog/pebble-dev-retreat-05.png) Some of Eric's key points: - Pebble sees value in being a platform - We're laser-focused on wearables, without legacy products to support - That means we can create great products, like hardware, firmware, apps and SDKs, that are extremely useful in a wearable context - While other devices will most likely be closed, Pebble is focused on creating an open, cross-platform ecosystem of watchapps In other sessions, between hacking on apps and getting code reviewed by Team Pebble, developers got to hear about the new tools, frameworks, and APIs that Team Pebble is currently working on and to which Pebble, as a leading software company in the wearable space, is devoting valuable engineering and developer resources, by Thomas Sarlandie, Pebble’s software evangelist who masterfully organized and set up the event. ![alt text](https://developer.repebble.com/assets/images/blog/pebble-dev-retreat-08.png) Later, developers were treated to a session on “Tips on Designing Apps” from Pebble's UX lead by Omar Younis, and “How to Optimize Energy Consumption on your Pebble App”, as well as an eye-opening guided tour inside the Pebble watch hardware by Andrew Witte, Pebble’s Chief Technology Officer. ![alt text](https://developer.repebble.com/assets/images/blog/pebble-dev-retreat-09.png) Meanwhile, Pebble developers had a chance to work hands-on with members of Team Pebble, side by side, at Pebble HQ in conference rooms, workshops, and even outside while enjoying great food, camaraderie and delicious BBQ’s in the California sun. The beauty of the event, IMHO, was that it worked both ways. ![alt text](https://developer.repebble.com/assets/images/blog/pebble-dev-retreat-12.png) Team Pebble got to listen, up close and personal, to the needs and concerns of this most important class of Pebble developers, software architects and designers -- the great folks who make up the Pebble developer community around the world. We got to learn from their coding experiences, collect feedback, log bugs, and post feature requests on a dozen different whiteboards at Pebble HQ. Hacking away over the weekend, I can tell you that these folks inspired Team Pebble to meet the programming gods head on and produce a next generation Pebble SDK that truly rocks the wearable software world out there. Onward and upward! ![alt text](https://developer.repebble.com/assets/images/blog/pebble-dev-retreat-11.png) Photos tell the story. Check them out, and if you’ve got any that you want to share, [contact us](https://developer.repebble.com/contact.md). I’ll do another posting and add your quotes and comments. ![alt text](https://developer.repebble.com/assets/images/blog/pebble-dev-retreat-15.png) As Luke Korth wrote in his blog: [http://lukekorth.com/blog/pebble-developer-retreat/](http://lukekorth.com/blog/pebble-developer-retreat/) > I've spent 5 days hanging out and hacking with the guys at Pebble and many of the active community members and it's been nothing short of spectacular. Everyone at Pebble is extremely dedicated to what they're building and more than happy to help developers and integrate requests into the Pebble SDK. It was also an awesome opportunity to meet a bunch of developers in the Pebble community and hear some of their ideas and thoughts about Pebble.... I really enjoyed spending time with the Pebble devs and I can't wait to see where they take things in the future. Paul Rode, the author of Watchface Generator app, said: > The Pebble Developer Retreat was a lot of fun and I'm happy with what's coming up in the near future. Also while being on my way back home, the generator exceeded 50.000 generated watchfaces. That rocks! [https://www.facebook.com/WatchfaceGenerator](https://www.facebook.com/WatchfaceGenerator) * * * --- # Screenshot Barcodes Tutorials And Mao Source: https://developer.repebble.com/blog/2013/07/26/Screenshot-Barcodes-Tutorials-And-Mao/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 26 July 2013 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/07/26/Screenshot-Barcodes-Tutorials-And-Mao.md#comments) It seems the summer days are only increasing the energy of Pebble developers! There were lots of new watchfaces [announced on the forums](https://forums.getpebble.com/categories/watchapp-directory) this week. It's getting hard to try all of them! Watchfaces are a very visible part of the work they are doing but they are also inventing new ways to use the Pebble SDK. Featured this week: how to take screenshots of your apps, getting facebook and twitter on your wrist (for iOS and Android), wearing barcodes and paying your respect to an old Chinese custom! ## Taking screenshots of Pebble apps The current SDK offers no easy way to make screenshots of your applications and most developers have resolved to either take pictures of their watch or mockup their interface in [Photoshop](https://forums.getpebble.com/discussion/4557/how-can-i-take-a-screenshot-of-a-pebble-watch-face) or with [HTML Canvas](http://blog.gazler.com/blog/2013/01/30/writing-your-first-pebble-watchface/). This was not good enough for [Edward](https://github.com/epatel/) who decided to hack around the SDK a little bit (we will forgive you this time!) and wrote a [small library](https://github.com/epatel/pblindex/blob/capture/src/pblcapture.c) that developers can embed in an existing app to export screen captures. The captures are received on an iPhone by his companion app and for those of you using httpebble, he has [even made a fork](https://github.com/epatel/httpebble-ios) of httpebble that includes screen capturing and sharing. Here is a little demo: Pretty awesome! ## Twitter, Facebook and more on the wrist [Mybucketz](http://www.mybucketz.com/) is a free service to aggregate all your social medias. [Al took the time](https://forums.getpebble.com/discussion/6694/watch-app-mybucketz-lets-you-see-your-facebook-and-twitter-feeds-on-your-wrist-httpebble#latest) to write [a mybucketz watch application](http://www.mypebblefaces.com/view?fID=5074&aName=mybucketz&pageTitle=Facebook%2FTwitter+Feed+%28mybucketz%29&auID=6107) that works on Android and iOS (with httpebble) and allows you to get all your timelines on your wrist. ![Mybucketz screenshot](http://media.tumblr.com/6a78dd497fb864285b9456dcf2d65dae/tumblr_inline_mqd83aDvMs1qz4rgp.jpg) ## Wear your barcode We already knew that you could load your [starbucks card](https://twitter.com/nickdube/status/331835268728512512) in a watchface and use it to pay for drinks at Starbucks. This has also been [reported to work](http://www.modaco.com/topic/363454-turn-your-pebble-into-a-tesco-clubcard-with-a-custom-watchface/) at Tesco in the UK. So far the [technique used](http://www.reddit.com/r/pebble/comments/1dze2a/grocery_store_loyalty_card_on_pebble/) involved generating an image online and creating a new watchface with that image embedded (thanks to [http://www.watchface-generator.de/](http://www.watchface-generator.de/) for example). This is now getting even easier thanks to [frethop](https://forums.getpebble.com/profile/1181/frethop) _Wear Your Barcode_ watchface. It is available on [mypebblefaces.com](http://www.mypebblefaces.com/view?fID=5165&aName=frethop&pageTitle=WYB%3A+Wear+Your+Barcode&auID=1336) and works with a companion [Android app](https://play.google.com/store/apps/details?id=com.kabestin.android.wristbarcode.view&hl=en): It supports carrying several barcodes in one watchface and you can save existing barcodes by scanning them with your phone. So what barcode will you wear? Let us know in the comments! ## Tutorials Still dreaming about developing for Pebble? This week brought more tutorials to help you get started! [This three page tutorial on The Register](http://www.theregister.co.uk/2013/07/18/how_to_write_apps_for_the_pebble_smartwatch/) is a great way to discover the SDK. It covers how to do some basic graphics on Pebble, using the buttons and pushing your app to the watch. By the end of this tutorial, you will have a nice bouncing ball type of app, like demonstrated in this video: If you want to dive deeper in the SDK, [Chris](http://twitter.com/Chris_DL) has just published [part 7](http://ninedof.wordpress.com/2013/07/21/pebble-watch-face-sdk-7-scrolllayers/) of his SDK Tutorial, covering `ScrollLayers`. If you missed them, the first part provided a good primer of basic C and the following posts cover different APIs of the SDK: installation, building a first watch face, animations, fonts and two-way communications. ## Dev Challenge When you are done reading those tutorials, you might want to give this [developer challenge](http://www.reddit.com/r/pebble/comments/1imaq5/dev_challenge_chairman_mao_watch_for_pebble) a try! The goal is to create a watchface that reproduces a classic Mao watch: > If you have ever visited China, an almost essential souvenir is the infamous Chairman Mao watch. The watch features a picture of Chairman Mao and his hand waves as it runs. It defininitely falls into the so-tacky-it's-awesome camp, and is a "communist kitsch" must have! The other thing about the watches is that, being cheaply made in China, they are also almost guaranteed to break after a few days of use.... ![Mao watch](https://developer.repebble.com/assets/images/blog/maowatch.gif) ## Coming up next week We cannot wait to see what new cool apps will come over the next week, make sure to let us know about your creations! * * * --- # Using Pebble System Fonts Source: https://developer.repebble.com/blog/2013/07/24/Using-Pebble-System-Fonts/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 24 July 2013 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2013/07/24/Using-Pebble-System-Fonts.md#comments) _(2014 09: This article was updated to add links for SDK 2.0 users and add the resource identifier of the fonts as suggested by Timothy Gray in the comments.)_ Just like any modern platform, typography is a very important element of Pebble user interface. As Designers and Developers, you have the choice to use one of Pebble system fonts or to embed your own. Pebble system fonts were chosen for their readability and excellent quality on Pebble black & white display, it's a good idea to know them well before choosing to embed your own. ## Using Pebble system fonts To use one of the system font, you need to call `fonts_get_system_font()` and pass it the name of a system font. Those are defined in `pebble_fonts.h` in the `include` directory of your project. This function returns a `GFont` object that you can use with `text_layer_set_text()` and `graphics_text_draw()`. For example, in our Hello World example we used the _Roboto Condensed 21_ font: ``` void handle\_init(AppContextRef ctx) {window\_init(&window, "Window Name");window\_stack\_push(&window, true /\* Animated \*/);text\_layer\_init(&hello\_layer, GRect(0, 65, 144, 30));text\_layer\_set\_text\_alignment(&hello\_layer, GTextAlignmentCenter);text\_layer\_set\_text(&hello\_layer, "Hello World!");text\_layer\_set\_font(&hello\_layer, fonts\_get\_system\_font(FONT\_KEY\_ROBOTO\_CONDENSED\_21));layer\_add\_child(&window.layer, &hello\_layer.layer);} ``` > **Update for SDK 2 users** : > > Refer to the [second part of the watchface tutorial](https://developer.repebble.com/getting-started/watchface-tutorial/part2/) for updated sample code. ## An illustrated list of Pebble system fonts **Notice** A more up-to-date list of fonts can be found by reading [_System Fonts_](https://developer.repebble.com/guides/app-resources/system-fonts.md) in the developer guides. To facilitate your choice, here is a series of screenshot of Pebble system fonts used to display digits and some text. | Gothic 14 | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_14_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_14_abc.png) | | Gothic 14 Bold | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_14_bold_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_14_bold_abc.png) | | Gothic 18 | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_18_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_18_abc.png) | | Gothic 18 Bold | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_18_bold_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_18_bold_abc.png) | | Gothic 24 | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_24_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_24_abc.png) | | Gothic 24 Bold | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_24_bold_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_24_bold_abc.png) | | Gothic 28 | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_28_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_28_abc.png) | | Gothic 28 Bold | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_28_bold_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/gothic_28_bold_abc.png) | | Bitham 30 Black | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_30_black_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_30_black_abc.png) | | Bitham 42 Bold | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_42_bold_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_42_bold_abc.png) | | Bitham 42 Light | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_42_light_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_42_light_abc.png) | | Bitham 34 Medium Numbers | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_34_medium_numbers_digits.png) | | | Bitham 42 Medium Numbers | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/bitham_42_medium_numbers_digits.png) | | | Roboto 21 Condensed | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/roboto_21_condensed_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/roboto_21_condensed_abc.png) | | Roboto 49 Bold Subset | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/roboto_49_bold_subset_digits.png) | | | Droid 28 Bold | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/droid_28_bold_digits.png) | ![](https://developer.repebble.com/assets/images/blog/pebble-fonts/droid_28_bold_abc.png) | ## A word of caution To save space on Pebble's memory, some of the system fonts only contain a subset of the default character set: - _Roboto 49 Bold Subset_ only contains digits and a colon; - _Bitham 34/42 Medium Numbers_ only contain digits and a colon; - _Bitham 18/34 Light Subset_ only contains a few characters and you should not use them (this why they are not included in the screenshots). ## Browse the fonts on your watch To help you decide which font is best, you will probably want to test those fonts directly on a watch. We have added a new demo application called [`app_font_browser`](https://github.com/pebble-examples/app-font-browser) to the SDK to help you do that. This application uses a [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") to navigate through the different options and when you have chosen a font, you can use the _Select_ button to cycle through different messages. ![app_font_browser](https://developer.repebble.com/assets/images/blog/app_font_browser.png) You can very easily customize the list of messages for your needs and as an exercise for the reader, you can adapt the app to show custom fonts as well! This example is available in the `Examples/watchapps/app_font_browser` of the SDK and on [Github](https://github.com/pebble-examples/app-font-browser). * * * --- # Awesome Apps Of The Week Source: https://developer.repebble.com/blog/2013/07/18/Awesome-Apps-Of-The-Week/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 18 July 2013 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [#makeawesomehappen](https://developer.repebble.com/blog/tags/makeawesomehappen/) - [Comments](https://developer.repebble.com/blog/2013/07/18/Awesome-Apps-Of-The-Week.md#comments) We love seeing what you guys are doing with the Pebble SDK! Every day there is more amazing apps that pushes the limit of what we thought was possible and contributes to [#makeawesomehappen](https://twitter.com/search?q=%23makeawesomehappen). Here is a selection of the apps that caught our attention this week. Developers, please let us know about your apps in the forums or on twitter - And if you make a great video like most of the apps shown this week, it's even easier for us to help you! ## Pebble Rocker - Facebook, Twitter, Find your phone and more! [Pebble Rocker](https://play.google.com/store/apps/details?id=com.smallrocksoftware.pebblerocker) by SmallRock Software is an amazing suite of apps for your phone and your watch. The list of features they managed to develop and release together is extremely impressive: - Browse your Facebook feed with comments and pictures - Check in with Foursquare to the nearest venues - Read your twitter timeline - Go through your phone's photos on the watch - And even a tip calculator and a phone pager! Last but not least, they made it extremely easy to install the apps on your watch - just install the android app and follow the guides. At $.99 we think it's a great deal and we cant wait to see what extra features the updates will bring! Not convinced yet? Check out this amazing video they made of the different apps: Now if there is one more thing we would like to see here, it's an iPhone version! (Pebble Rocker is only available on Android right now). ## Pebble Canvas - Create new watchface from your phone Most of you are probably familiar with [watchface-generator.de](http://http://www.watchface-generator.de/) to generate new watchface online without any coding. [Steve Penna](https://twitter.com/stevepenna) took that concept and wrote [Pebble Canvas](https://play.google.com/store/apps/details?id=com.pennas.pebblecanvas) an Android application that allows you to create new watchfaces directly from your phone. Not only can you create watchfaces, [Pebble Canvas](https://play.google.com/store/apps/details?id=com.pennas.pebblecanvas) can also include dynamic information from your phone such as cell-coverage, battery status, unread emails, etc. [![Pebble Canvas](https://developer.repebble.com/assets/images/blog/pebble-canvas.png)](https://play.google.com/store/apps/details?id=com.pennas.pebblecanvas) Pebble Canvas is [available for free](https://play.google.com/store/apps/details?id=com.pennas.pebblecanvas) on the Android Play store. ## Synchronize your Pebble with Evernote A few weeks ago [Evernote organized their annual DevCup](http://blog.evernote.com/tech/2013/05/13/evernotehonda-design-build-weekend/) and Pebble Team joined with some Pebbles and energy to help the teams interested in building a true multi-device experience. Today we would like to feature two apps that were built for the cup and will come handy in a lot of situations - including "preparing for exams"(sic)! [WatchNote](https://www.hackerleague.org/hackathons/evernote-devcup-2013/hacks/watchnote) lets you synchronize some notes between your phone, Evernote and your Pebble. [Chronometry](https://www.hackerleague.org/hackathons/evernote-devcup-2013/hacks/chronometry-simple-time-tracker) helps you track your time, directly from Pebble and sends timesheets directly to your Evernote! ## Still not sure about writing apps? Check out [SteamCab](http://steamcab.wordpress.com/) [series on how he became a Pebble developer](http://steamcab.wordpress.com/2013/07/14/hacking-the-pebble-smart-watch-step-1-obtain-a-pebble-watch/). Lots of useful links and information in there! Keep sending your apps, videos and blog posts - we will share them next week! * * * --- # Lots Of Goodies In Pebble Sdk 1.12 Source: https://developer.repebble.com/blog/2013/07/03/Lots-of-goodies-In-Pebble-SDK-1.12/ This is the Pebble Developer Blog. For general Pebble news and announcements, visit the main blog at [repebble.com/blog](https://repebble.com/blog). - 03 July 2013 - [Thomas Sarlandie](https://developer.repebble.com/blog/authors/thomas/) - [Freshly Baked](https://developer.repebble.com/blog/tags/freshly-baked/), [Beautiful Code](https://developer.repebble.com/blog/tags/beautiful-code/) - [Comments](https://developer.repebble.com/blog/2013/07/03/Lots-of-goodies-In-Pebble-SDK-1.12.md#comments) [Pebble firmware 1.12](https://forums.getpebble.com/discussion/6222/announcing-pebbleos-v1-12-firmware-release-notes/1) was released earlier this week. The changelog announced "lots of goodies for watchapp developers" and many of you are curious to see what they are! We are releasing today the new updated SDK for this firmware version. The first change you will notice is that the SDK version now follows the version numbering of the firmware. The other change is that we are clarifying the naming scheme for the different components: - Pebble SDK is the development kit to write watchapps and watchfaces; - PebbleKit is the mobile-phone side of the SDK - It's available for iOS and Android and follows the same version number as the firmware. PebbleKit is distributed inside the Pebble SDK. And as always, you can grab it from [your Pebble account](https://developer.repebble.com/sdk/download). This new release of the SDK introduces one major new feature and a lot of improvements: - [AppLogging](https://developer.repebble.com/docs/c/Foundation/Logging.md "Logging"): A framework to send log messages from the watch to your laptop over Bluetooth; - Several standard C functions are now available to developers: `snprintf`, `time`, `memcpy`, `memmove`, etc. - A new compositing mode for bitmaps - Examples for MenuLayer, SimpleMenuLayer and ScrollLayer - Several bugfixes (listed in the [changelog](https://developer.repebble.com/sdk/changelogs/)) ## Install or update the new SDK [Instructions to install the SDK](https://developer.repebble.com/sdk.md) have not changed. Once installed, you can upgrade existing projects with: ``` $ cd watchapp $ ~/pebble-dev/PebbleSDK-v1.1.2/Pebble/tools/create_pebble_project.py --update-symlink ~/pebble-dev/PebbleSDK-v1.1.2/Pebble/sdk . $ ./waf configure $ ./waf build ``` ## AppLogging The first thing you will need to do to use AppLogging is to enable it in your watch. There is a new option for it in the 'About' menu: ![Enable AppLogging in the 'About' menu](https://developer.repebble.com/assets/images/blog/applogging-enabled.jpg) In your application, use `APP_LOG(level, fmt, args...)` to send new log messages through Bluetooth. ``` APP_LOG(APP_LOG_LEVEL_INFO, "Hello World!"); ``` AppLogging provides five log levels (error, warning, info, debug and debug\_verbose) which 'ought to be enough for everyone'. On your computer, you will need the latest version of [libpebble from our repository](https://github.com/coredevices/libpebble3). Pair your computer to your watch and run: ``` $ ~/pebble-dev/libpebble/p.py --lightblue --pebble_id 00:18:33:XX:XX:XX logcat ``` We have a complete example ready to go in `Pebble/demos/feature_app_logging/`. ## Standard C functions By popular requests, we have added: - `srand()` and `rand()` to generate random numbers; - Some very useful string functions: `snprintf()`, `strcmp()`/`strncmp()`, `strcpy()`/`strncpy()`, `strcat()`/`strncat()`, `strlen()` - Standard UNIX time functions: `time()` (number of seconds since epoch) and `time_ms()` (number of millisecond since beginning of second) You will find documentation for those functions in the [reference documentation](https://developer.repebble.com/docs/c.md). ## A new compositing mode We added a new compositing mode `GCompOpSet` for completeness sake (we had all the other options but this one) and we reworked [the documentation](https://developer.repebble.com/docs/c/Graphics/Graphics_Types.md#GCompOp "GCompOp") to be accurate. ![Compositing modes](https://developer.repebble.com/assets/images/blog/compops.png) There is also a new example (`feature_draw_bitmap_composite`) to demonstrate the different compositing modes. ## New examples Finally we added examples for `MenuLayer`, `SimpleMenuLayer` and `ScrollLayer`. We hope you will find them useful! ## @pebbledev We are also happy to announce that we will start tweeting developer news on [@pebbledev](https://twitter.com/pebbledev) - Please follow us and don't hesitate to mention us when you have great watch apps to share! Feel free to discuss this update here or in the forums! * * *