---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Platform-specific Resources
description: |
  How to include different resources for different platforms, as well as how to
  include a resource only on a particular platform.
guide_group: app-resources
order: 6
---

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      |
|---------|------------|------------|---------|------------|
| rect    | rect       | round      | rect    | rect       |
| bw      | color      | color      | bw      | color      |
| aplite  | basalt     | chalk      | diroite | emery      |
| 144w    | 144w       | 180w       | 144w    | 220w       |
| 168h    | 168h       | 180h       | 168h    | 228h       |
| compass | compass    | compass    |         | compass    |
|         | mic        | mic        | mic     | mic        |
|         | strap      | strap      | strap   | strap      |
|         | strappower | strappower |         | strappower |
|         | 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.

```text
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"
      ]
    }
  ]
}
```


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Raw Data Files
description: |
  How to add raw data resources to a project and read them in your app.
guide_group: app-resources
order: 7
platform_choice: true
---

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`` and ``GBitmapSequence``, which both use raw resources
as input files. Other possible applications include localized string
dictionaries, CSV data files, etc.

## Adding Raw Data Files

{% platform local %}
To add a file as a raw resource, specify its `type` as `raw` in `package.json`.
An example is shown below:

```js
"resources": {
  "media": [
    {
      "type": "raw",
      "name": "EXAMPLE_DATA_FILE",
      "file": "data.bin"
    }
  ]
}
```
{% endplatform %}

{% platform cloudpebble %}
To add a file as a raw resource, click 'Add New' in the Resources section of the
sidebar, and set the 'Resource Type' as 'raw binary blob'.
{% endplatform %}

## 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:

```c
// Get resource handle
ResHandle 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:

```c
// Get size of the resource in bytes
size_t res_size = resource_size(handle);
```

To read bytes from the resource, create an appropriate byte buffer and copy data
into it:

```c
// Create a buffer the exact size of the raw resource
uint8_t *s_buffer = (uint8_t*)malloc(res_size);
```

The example below copies the entire resource into a `uint8_t` buffer:

```c
// Copy all bytes to a buffer
resource_load(handle, s_buffer, res_size);
```

It is also possible to read a specific range of bytes from a given offset into
the buffer:

```c
// Read the second set of 8 bytes
resource_load_byte_range(handle, 8, s_buffer, 8);
```

This is the latest version of the internal repository from Pebble Technology providing the software to run on Pebble watches. Proprietary source code has been removed from this repository and it will not compile as-is. This is for information only.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: System Fonts
description: |
  A complete list of all the system fonts available for use in Pebble projects.
guide_group: app-resources
order: 8
---

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
    Preview

      FONT_KEY_GOTHIC_14

      FONT_KEY_GOTHIC_14_BOLD

      FONT_KEY_GOTHIC_18

      FONT_KEY_GOTHIC_18_BOLD

      FONT_KEY_GOTHIC_24

      FONT_KEY_GOTHIC_24_BOLD

      FONT_KEY_GOTHIC_28

      FONT_KEY_GOTHIC_28_BOLD

### Bitham

    Available Font Keys
    Preview

      FONT_KEY_BITHAM_30_BLACK

      FONT_KEY_BITHAM_34_MEDIUM_NUMBERS

      FONT_KEY_BITHAM_42_BOLD

      FONT_KEY_BITHAM_42_LIGHT

      FONT_KEY_BITHAM_42_MEDIUM_NUMBERS

### Roboto/Droid Serif

    Available Font Keys
    Preview

      FONT_KEY_ROBOTO_CONDENSED_21

      FONT_KEY_ROBOTO_BOLD_SUBSET_49

      FONT_KEY_DROID_SERIF_28_BOLD

### LECO

    Available Font Keys
    Preview

      FONT_KEY_LECO_20_BOLD_NUMBERS

      FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM

      FONT_KEY_LECO_28_LIGHT_NUMBERS

      FONT_KEY_LECO_32_BOLD_NUMBERS

      FONT_KEY_LECO_36_BOLD_NUMBERS

      FONT_KEY_LECO_38_BOLD_NUMBERS

      FONT_KEY_LECO_42_NUMBERS

## 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``, or ``graphics_draw_text()``:

```c
text_layer_set_text(s_layer, "Smiley face: \U0001F603");
```

An app containing a ``TextLayer`` displaying the above string will look similar
to this:

![emoji-screenshot >{pebble-screenshot,pebble-screenshot--steel-black}](/images/guides/pebble-apps/resources/emoji-screenshot.png)

The supported characters are displayed below with their corresponding unicode
values.

### Deprecated Emoji Symbols

The following emoji characters are no longer available on the Aplite platform.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Appstore Analytics
description: |
  How to view and use analytics about your app's usage.
guide_group: appstore-publishing
order: 2
---

After publishing an app, developers can view automatic analytical data and
graphs for a variety of metrics. These can be used to help track the performance
of an app, identify when crashes start occuring, measure how fast an update is
adopted, or which platforms are most popular with users.

> Due to latencies in the analytics gathering process, data from the last 7 days
> may not be very accurate.

## Available Metrics

Several different metrics are available to developers, and are reported on a
daily basis. These metrics can be sorted or viewed according to a number of
categories:

* App version - Which release of the app the user is running.

* Hardware platform - Which Pebble hardware version the user is wearing.

* Mobile platform - Which mobile phone platform (such as Android or iOS) the
  user is using.

An example graph is shown with each metric type below, grouped by hardware
platform.

### Installations

The total number of times an app has been installed.

![installations-example](/images/guides/appstore-publishing/installations-example.png)

### Unique Users

The total number of unique users who have installed the app. This is
different to the installation metric due to users installing the same app
multiple times.

![unique-users-example](/images/guides/appstore-publishing/unique-users-example.png)

### Launches

The total number of times the app has been launched.

![launches-example](/images/guides/appstore-publishing/launches-example.png)

### Crash Count

The total number of times the app has crashed. Use the filters to view
crash count by platform or app version to help identify the source of a crash.

![crash-count-example](/images/guides/appstore-publishing/crash-count-example.png)

### Run Time

The total run time of the app in hours.

![run-time-example](/images/guides/appstore-publishing/run-time-example.png)

### Run Time per launch

The average run time of the app each time it was launched in minutes.

![run-time-per-launch-example](/images/guides/appstore-publishing/run-time-per-launch-example.png)

### Buttons Pressed Per Launch

> Watchfaces only

The average number of button presses per launch of the app.

![buttons-pressed-example](/images/guides/appstore-publishing/buttons-pressed-example.png)

### Timeline: Users Opening Pin

> Timeline-enabled apps only

The number of users opening timeline pins associated with the app.

![opening-pin-example](/images/guides/appstore-publishing/opening-pin-example.png)

### Timeline: Pins Opened

> Timeline-enabled apps only

The number of timeline pins opened.

![pins-opened-example](/images/guides/appstore-publishing/pins-opened-example.png)

### Timeline: Users Launching App from Pin

> Timeline-enabled apps only

The number of users launching the app from a timeline pin.

![launching-app-from-pin-example](/images/guides/appstore-publishing/launching-app-from-pin-example.png)

### Timeline: Times App Launched from Pin

> Timeline-enabled apps only

Number of times the app was launched from a timeline pin.

![times-launched-example](/images/guides/appstore-publishing/times-launched-example.png)

## Battery Stats

In addition to installation, run time, and launch statistics, developers can
also view a battery grade for their app. Grade 'A' is the best available,
indicating that the app is very battery friendly, while grade 'F' is the
opposite (the app is severely draining the user's battery).

> An app must reach a certain threshold of data before battery statistics can be
> reliably calculated. This is around 200 users.

This is calculated based upon how much a user's battery decreased while the app
was open, and so does not take into other factors such as account notifications
or backlight activity during that time.

![grade](/images/guides/appstore-publishing/grade.png)

Clicking 'View More Details' will show a detailed breakdown for all the data
available across all app versions.

![grade-versions](/images/guides/appstore-publishing/grade-versions.png)


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Appstore Assets
description: |
  A collection of downloads to help developers create and improve their appstore
  listings.
guide_group: appstore-publishing
order: 3
---

A number of graphical assets are required when publishing an app on the
[Developer Portal](https://dev-portal.getpebble.com/), such as a marketing
banners. The resources on this page serve to help developers give these assets a
more authentic feel.

## Example Marketing Banners

Example
[marketing banners](https://s3.amazonaws.com/developer.getpebble.com/assets/other/banner-examples.zip)
from existing apps may help provide inspiration for an app's appstore banner
design. Readable fonts, an appropriate background image, and at least one framed
screenshot are all recommended.

## Marketing Banner Templates

![](/images/guides/appstore-publishing/perspective-right.png =400x)

Use these
[blank PSD templates](https://s3.amazonaws.com/developer.getpebble.com/assets/other/banner-templates-design.zip)
to start a new app marketing banner in Photoshop from a template.

## App Screenshot Frames

Use these
[example screenshot frames](https://s3.amazonaws.com/developer.getpebble.com/assets/other/pebble-frames.zip)
for Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, and Pebble Time Round
to decorate app screenshots in marketing banners and other assets.

> Note: The screenshots added to the listing itself must not be framed.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Appstore Publishing
description: |
  How to get your app ready for going live in the Pebble appstore.
guide_group: appstore-publishing
menu: false
permalink: /guides/appstore-publishing/
generate_toc: false
hide_comments: true
---

When a developer is happy that their app is feature-complete and stable, they
can upload the compiled `.pbw` file to the
[Developer Portal](https://dev-portal.getpebble.com) to make it available on the
Pebble appstore for all users with compatible watches to share and enjoy.

In order to be successfully listed in the Pebble appstore the developer must:

* Provide all required assets and marketing material.

* Provide at least one `.pbw` release.

* Use a unique and valid UUID.

* Build their app with a non-beta SDK.

* Ensure their app complies with the various legal agreements.

Information on how to meet these requirements is given in this group of guides,
as well as details about available analytical data for published apps and
example asset material templates.

## Contents

{% include guides/contents-group.md group=page.group_data %}


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Preparing a Submission
description: |
  How to prepare an app submission for the Pebble appstore.
guide_group: appstore-publishing
order: 0
---

Once a new Pebble watchface or watchapp has been created, the
[Pebble Developer Portal](https://dev-portal.getpebble.com/) allows the
developer to publish their creation to the appstore either publicly, or
privately. The appstore is built into the official mobile apps and means that
every new app can be found and also featured for increased exposure and
publicity.

> Note: An app can only be published privately while it is not already published
> publicly. If an app is already public, it must be unpublished before it can be
> made private.

To build the appstore listing for a new app, the following resources are
required from the developer. Some may not be required, depending on the type of
app being listed. Read
{% guide_link appstore-publishing/publishing-an-app#listing-resources "Listing Resources" %}
for a comparison.

## Basic Info

| Resource | Details |
|----------|---------|
| App title | Title of the app. |
| Website URL | Link to the brand or other website related to the app. |
| Source code URL | Link to the source code of the app (such as GitHub or BitBucket). |
| Support email address | An email address for support issues. If left blank, the developer's account email address will be used. |
| Category | A watchapp may be categorized depending on the kind of functionality it offers. Users can browse the appstore by these categories. |
| Icons | A large and small icons representing the app. |

## Asset Collections

An asset collection must be created for each of the platforms that the app
supports. These are used to tailor the description and screenshots shown to
users browing with a specific platform connected.

| Resource | Details |
|----------|---------|
| Description | The details and features of the app. Maximum 1600 characters. |
| Screenshots | Screenshots showing off the design and features of the app. Maximum 5 per platform in PNG, GIF, or Animated GIF format. |
| Marketing banner | Large image used at the top of a listing in some places, as well as if an app is featured on one of the main pages. |

## Releases

In addition to the visual assets in an appstore listing, the developer must
upload at least one valid release build in the form of a `.pbw` file generated
by the Pebble SDK. This is the file that will be distributed to users if they
choose to install your app.

The appstore will automatically select the appropriate version to download based
on the SDK version. This is normally the latest release, with the one exception
of the latest release built for SDK 2.x (deprecated) distributed to users
running a watch firmware less than 3.0. A release is considered valid if the
UUID is not in use and the version is greater than all previously published
releases.

## Companion Apps

If your app requires an Android or iOS companion app to function, it can be
listed here by providing the name, icon, and URL that users can use to obtain
the companion app. When a user install the watchapp, they will be prompted to
also download the companion app automatically.

## Timeline

Developers that require the user of the timeline API will need to click 'Enable
timeline' to obtain API keys used for pushing pins. See the
{% guide_link pebble-timeline %} guides for more information.

## Promotion

Once published, the key to growth in an app is through promotion. Aside from
users recommending the app to each other, posting on websites such as the
[Pebble Forums](https://forums.getpebble.com/categories/watchapp-directory),
[Reddit](https://www.reddit.com/r/pebble), and [Twitter](https://twitter.com)
can help increase exposure.

## Developer Retreat Video

Watch the presentation given by Aaron Cannon at the 2016 Developer Retreat to
learn more about preparing asset collections for the appstore.

[EMBED](//www.youtube.com/watch?v=qXmz3eINObU&index=10&list=PLDPHNsf1sb48bgS5oNr8hgFz0pL92XqtO)


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Publishing an App
description: |
  How to upload and publish an app in the Pebble appstore.
guide_group: appstore-publishing
order: 1
---

When an app is ready for publishing, the `.pbw` file needs to be uploaded to the
Pebble [Developer Portal](https://dev-portal.getpebble.com/), where a listing is
created. Depending on the type of app, different sets of additional resources
are required. These resources are then used to generate the listing pages
visible to potential users in the Pebble appstore, which is embedded within the Pebble mobile app.

You can also view the [watchfaces](http://apps.getpebble.com/en_US/watchfaces)
and [watchapps](http://apps.getpebble.com/en_US/watchapps) from a desktop
computer, as well as perform searches and get shareable links.

## Listing Resources

The table below gives a summary of which types of resources required by
different types of app. Use this to quickly assess how complete assets and
resources are before creating the listing.

| Resource | Watchface | Watchapp | Companion |
|----------|-----------|----------|-----------|
| Title | Yes | Yes | Yes |
| `.pbw` release build | Yes | Yes | - |
| Asset collections | Yes | Yes | Yes |
| Category | - | Yes | Yes |
| Large and small icons | - | Yes | Yes |
| Compatible platforms | - | - | Yes |
| Android or iOS companion appstore listing | - | - | Yes |

## Publishing a Watchface

1. After logging in, click 'Add a Watchface'.

2. Enter the basic details of the watchface, such as the title, source code URL,
   and support email (if different from the one associated with this developer
   account):

    ![face-title](/images/guides/appstore-publishing/face-title.png)

3. Click 'Create' to be taken to the listing page. This page details the status
   of the listing, including links to subpages, a preview of the public page,
   and any missing information preventing release.

    ![face-listing](/images/guides/appstore-publishing/face-listing.png)

4. The status now says 'Missing: At least one published release'. Click 'Add a
   release' to upload the `.pbw`, optionally adding release notes:

    ![face-release](/images/guides/appstore-publishing/face-release.png)

5. Click 'Save'. After reloading the page, make the release public by clicking
   'Publish' next to the release:

    ![face-release-publish](/images/guides/appstore-publishing/face-release-publish.png)

6. The status now says 'Missing: A complete X asset collection' for
   each X supported platform. Click 'Manage Asset Collections', then click
   'Create' for a supported platform.

7. Add a description, up to 5 screenshots, and optionally a marketing banner
   before clicking 'Create Asset Collection'.

    ![face-assets](/images/guides/appstore-publishing/face-assets.png)

8. Once all asset collections required have been created, click 'Publish' or
   'Publish Privately' to make the app available only to those viewing it
   through the direct link. Note that once made public, an app cannot then be
   made private.

9. After publishing, reload the page to get the public appstore link for social
   sharing, as well as a deep link that can be used to directly open the
   appstore in the mobile app.

## Publishing a Watchapp

1. After logging in, click 'Add a Watchapp'.

2. Enter the basic details of the watchapp, such as the title, source code URL,
   and support email (if different from the one associated with this developer
   account):

    ![app-title](/images/guides/appstore-publishing/app-title.png)

3. Select the most appropriate category for the app, depending on the features
   it provides:

    ![app-category](/images/guides/appstore-publishing/app-category.png)

4. Upload the large and small icons representing the app:

    ![app-icons](/images/guides/appstore-publishing/app-icons.png)

5. Click 'Create' to be taken to the listing page. This page details the status
   of the listing, including links to subpages, a preview of the public page,
   and any missing information preventing release.

    ![app-listing](/images/guides/appstore-publishing/app-listing.png)

6. The status now says 'Missing: At least one published release'. Click 'Add a
   release' to upload the `.pbw`, optionally adding release notes:

    ![app-release](/images/guides/appstore-publishing/app-release.png)

7. Click 'Save'. After reloading the page, make the release public by clicking
   'Publish' next to the release:

    ![face-release-publish](/images/guides/appstore-publishing/face-release-publish.png)

8. The status now says 'Missing: A complete X asset collection' for
   each X supported platform. Click 'Manage Asset Collections', then click
   'Create' for a supported platform.

9. Add a description, up to 5 screenshots, optionally up to three header images,
   and a marketing banner before clicking 'Create Asset Collection'.

    ![app-assets](/images/guides/appstore-publishing/app-assets.png)

10. Once all asset collections required have been created, click 'Publish' or
    'Publish Privately' to make the app available only to those viewing it
    through the direct link.

11. After publishing, reload the page to get the public appstore link for social
    sharing, as well as a deep link that can be used to directly open the
    appstore in the mobile app.

## Publishing a Companion App

> A companion app is one that is written for Pebble, but exists on the Google
> Play store, or the Appstore. Adding it to the Pebble appstore allows users to
> discover it from the mobile app.

1. After logging in, click 'Add a Companion App'.

2. Enter the basic details of the companion app, such as the title, source code
   URL, and support email (if different from the one associated with this
   developer account):

    ![companion-title](/images/guides/appstore-publishing/companion-title.png)

3. Select the most appropriate category for the app, depending on the features
   it provides:

    ![companion-category](/images/guides/appstore-publishing/companion-category.png)

4. Check a box beside each hardware platform that the companion app supports.
   For example, it may be a photo viewer app that does not support Aplite.

5. Upload the large and small icons representing the app:

    ![companion-icons](/images/guides/appstore-publishing/companion-icons.png)

6. Click 'Create' to be taken to the listing page. The status will now read
   'Missing: At least one iOS or Android application'. Add the companion app
   with eithr the 'Add Android Companion' or 'Add iOS Companion' buttons (or
   both!).

7. Add the companion app's small icon, the name of the other appstore app's
   name, as well as the direct link to it's location in the appropriate
   appstore. If it has been compiled with a PebbleKit 3.0, check that box:

    ![companion-link](/images/guides/appstore-publishing/companion-link.png)

8. Once the companion appstore link has been added, click 'Publish' or 'Publish
   Privately' to make the app available only to those viewing it through the
   direct link.

9. After publishing, reload the page to get the public appstore link for social
   sharing, as well as a deep link that can be used to directly open the
   appstore in the mobile app.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: iOS App Whitelisting
description: |
  Instructions to iOS developers to get their Pebble apps whitelisted.
guide_group: appstore-publishing
generate_toc: false
order: 4
---

Pebble is part of the Made For iPhone program, a requirement that hardware
accessories must meet to interact with iOS apps. If an iOS app uses PebbleKit
iOS, it must be whitelisted **before** it can be submitted to the Apple App
Store for approval.

## Requirements

* The iOS companion app must only start communication with a Pebble watch on
  an explicit action in the UI. It cannot autostart upon connection and it must
  stop whenever the user stops using it. Refer to the
  {% guide_link communication/using-pebblekit-ios %} guide for details.

* `com.getpebble.public` is the only external accessory protocol that can be
  used by 3rd party apps. Make sure this is listed in the `Info.plist` in the
  `UISupportedExternalAccessoryProtocols` array.

* Pebble may request a build of the iOS application. If this happens, the
  developer will be supplied with UDIDs to add to the provisioning profile.
  TestFlight/HockeyApp is the recommended way to share builds with Pebble.

[Whitelist a New App >{center,bg-lightblue,fg-white}](http://pbl.io/whitelist)

After whitelisting of the new app has been confirmed, add the following
information to the "Review Notes" section of the app's Apple app submission:

  MFI PPID 126683-0003

> Note: An iOS app does not need to be re-whitelisted every time a new update is
> released. However, Pebble reserves the right to remove an application from the
> whitelist if it appears that the app no longer meets these requirements.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Building for Every Pebble
description: How to write one app compatible with all Pebble smartwatches.
guide_group: best-practices
order: 0
---

The difference in capabilities between the various Pebble hardware platforms are
listed in
{% guide_link tools-and-resources/hardware-information %}. For example, the
Basalt, Chalk and Emery platforms support 64 colors, whereas the Aplite and
Diorite 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`` API should be excluded on platforms with no microphone:

```c
#if defined(PBL_MICROPHONE)
  // Start dictation UI
  dictation_session_start(s_dictation_session);
#else
  // Microphone is not available
  text_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:

```c
#if defined(PBL_COLOR)
  text_layer_set_text_color(s_text_layer, GColorRed);
  text_layer_set_background_color(s_text_layer, GColorChromeYellow);
#else
  text_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()`` macro.

```c
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_MICROPHONE` | `PBL_IF_MICROPHONE_ELSE()` | Running on hardware that includes a microphone. |
| `PBL_COMPASS` | None | Running on hardware that includes a compass. |
| `PBL_SMARTSTRAP` | `PBL_IF_SMARTSTRAP_ELSE()` | Running on hardware that includes a smartstrap connector, but does not indicate that the connector is capable of supplying power. |
| `PBL_SMARTSTRAP_POWER` | None | Running on hardware that includes a smartstrap connector capable of supplying power. |
| `PBL_HEALTH` | `PBL_IF_HEALTH_ELSE()` | Running on hardware that supports Pebble Health and the `HealthService` API. |
| `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_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_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.

## 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:

```c
#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:

```c
static void window_load(Window *window) {
  // Create a full-screen Layer - BAD
  s_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 and Diorite, but not on Chalk or Emery. This kind of screen
size-dependant calculation should use the ``UnobstructedArea`` bounds of the
``Window`` itself:

```c
static void window_load(Window *window) {
  // Get the unobstructed bounds of the Window
  Layer window_layer = window_get_root_layer(window);
  GRect window_bounds = layer_get_unobstructed_bounds(window_layer);

  // Properly create a full-screen Layer - GOOD
  s_some_layer = layer_create(window_bounds);
}
```

Another common use of this sort of construction is to make a ``Layer`` that is
half the unobstructed screen height. This can also be correctly achieved using
the ``Window`` unobstructed bounds:

```c
GRect layer_bounds = window_bounds;
layer_bounds.size.h /= 2;

// Create a Layer that is half the screen height
s_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`` 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.

```c
#if PBL_DISPLAY_HEIGHT == 228
  uint8_t offset_y = 100;
#elif PBL_DISPLAY_HEIGHT == 180
  uint8_t offset_y = 80;
#else
  uint8_t offset_y = 60;
#endif
```

> 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`` 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``.

```c
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 parameters
  switch(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 frame
  GRect layer_frame = GRect(0, y_offset, window_bounds.size.w, button_height);

  // Create the Layer
  s_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`` values to theme an app for each
available color of Pebble.

```c
static void window_load(Window *window) {
  GColor text_color, background_color;

  // Choose different theme colors per watch color
  switch(watch_info_get_color()) {
    case WATCH_INFO_COLOR_RED:
      // Red theme
      text_color = GColorWhite;
      background_color = GColorRed;
      break;
    case WATCH_INFO_COLOR_BLUE:
      // Blue theme
      text_color = GColorBlack;
      background_color = GColorVeryLightBlue;
      break;

    /* Other cases */

    default:
      text_color = GColorBlack;
      background_color = GColorWhite;
      break;

  }

  // Use the conditionally set value
  text_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.

```js
// Get the watch info
var info = Pebble.getActiveWatchInfo();

console.log('Pebble model: ' + info.model);
```

## Detecting Platform-specific JS Features

A number of features in PebbleKit JS (such as ``Pebble.timelineSubscribe()`` and
``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()``:

```js
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 or Diorite 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
{% guide_link app-resources/platform-specific %}.

## 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:

```c
#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()`` and ``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:

```c
// Conditionally print out the shape of the display
printf("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``
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.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Conserving Battery Life
description: How to write an app to consume power as efficiently as possible.
guide_group: best-practices
order: 1
related_docs:
  - Animation
  - Timer
  - AccelerometerService
  - BatteryStateService
  - TickTimerService
  - CompassService
  - Vibes
  - Light
related_examples:
  - title: Pebble Glancing Demo
    url: https://github.com/pebble-hacks/pebble_glancing_demo
---

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.

## Battery Ratings

Any app published in the [Developer Portal](https://dev-portal.getpebble.com)
will have a battery grade associated with it, once a minimum threshold of data
has been collected. This can be used to get a rough idea of how much battery
power the app consumes. For watchfaces and apps that will be launched for long
periods of time, making sure this grade is in the A - C range should be a
priority. Read {% guide_link appstore-publishing/appstore-analytics %} to learn
more about this rating system.

## 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``
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.

```c
static void tick_handler(struct tm *tick_time, TimeUnits changed) {
  // Update time
  set_time_digits(tick_time);

  // Only update once a minute
  if(tick_time->tm_sec == 0) {
    play_animation();
  }
}
```

This also applies to apps that make use of short-interval ``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:

```c
static void accel_tap_handler(AccelAxisType axis, int32_t direction) {
  // Animate when the user flicks their wrist
  play_animation();
}
```

```c
accel_tap_service_subscribe(tap_handler);
```

### Tick Updates

Many watchfaces unecessarily tick once a second by using the ``SECOND_UNIT``
constant value with the ``TickTimerService``, when they only update the display
once a minute. By using the ``MINUTE_UNIT`` instead, the amount of times the
watch is woken up per minute is reduced.

```c
// Only tick once a minute, much more time asleep
tick_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`` 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`` 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.

```c
// Batch samples into sets of 10 per callback
const uint32_t num_samples = 10;

// Sample at 10 Hz
accel_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`` 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.

```c
// Only update if the heading changes significantly
compass_service_set_heading_filter(TRIG_MAX_ANGLE / 36);
```

In addition, making frequent use of the ``Dictation`` API will also keep the
watch awake, and also incur a penalty for keeping the Bluetooth connection
alive. Consider using the ``Storage`` API to remember previous user input and
instead present a list of previous inputs if appropriate to reduce usage of this
API.

```c
static void dictation_session_callback(DictationSession *session, DictationSessionStatus status,
                                       char *transcription, void *context) {
  if(status == DictationSessionStatusSuccess) {
    // Display the dictated text
    snprintf(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`` 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`` 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`` 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:

```c
// Return to low power Bluetooth state
app_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`` to
execute common actions with one button press instead of a long scrolling
``MenuLayer``.

While the ``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:

```c
// Return to automatic backlight control
light_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`` 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.

[EMBED](//www.youtube.com/watch?v=TS0FPfgxAso)


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Best Practices
description: |
  Information to help optimize apps and ensure a good user experience.
guide_group: best-practices
menu: false
permalink: /guides/best-practices/
generate_toc: false
hide_comments: true
---

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

{% include guides/contents-group.md group=page.group_data %}


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Modular App Architecture
description: |
  How to break up a complex app into smaller pieces for managablilty, modularity
  and reusability.
guide_group: best-practices
order: 3
related_examples:
  - title: Modular App Example
    url: https://github.com/pebble-examples/modular-app-example/
---

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``s, perform communication over ``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``, ``Layer``, ``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``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:

```bash
$ 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`` and a child ``TextLayer``.

```text
modular-project/
  resources/
  src/
    modular-project.c
  package.json
  wscript
```

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 main `src`
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:

```text
modular-project/
  resources/
  src/
    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/modules/data.h`

```c
#pragma once         // Prevent errors by being included multiple times

#include <pebble.h>  // Pebble SDK symbols

void 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/modules/data.c`

```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`` lifecycle makes the task of keeping each ``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`` in its own
module. The first ``Window``'s code can be moved out of `src/modular-project.c`
into a new module in `src/windows/` called 'main_window':

`src/windows/main_window.h`

```c
#pragma once

#include <pebble.h>

void main_window_push();
```

`src/windows/main_window.c`

```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`` 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/main.c`

```c
#include <pebble.h>

#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:

```text
modular-project/
  resources/
  src/
    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.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Advanced Communication
description: |
  Details of communication tips and best practices for more advanced scenarios.
guide_group: communication
order: 0
related_docs:
  - AppMessage
related_examples:
  - title: JS Ready Example
    url: https://github.com/pebble-examples/js-ready-example
  - title: List Items Example
    url: https://github.com/pebble-examples/list-items-example
  - title: Accel Data Stream
    url: https://github.com/pebble-examples/accel-data-stream
  - title: PNG Download Example
    url: https://github.com/pebble-examples/png-download-example
  - title: Pebble Faces
    url: https://github.com/pebble-examples/pebble-faces
platform_choice: true
---

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
{% guide_link communication/using-pebblekit-js "PebbleKit JS" %} **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:`.

{% markdown %}
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:

```js
"messageKeys": [
  "JSReady"
]
```
{% endmarkdown %}

{% markdown %}
A simple method is to define a key in Settings that will be interpreted by
the watchapp to mean that the JS environment is ready for exchange data:

* JSReady
{% endmarkdown %}

The watchapp should implement a variable that describes if the `ready` event has
occured. An example is shown below:

```c
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.

```c
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:

```js
Pebble.addEventListener('ready', function() {
  console.log('PebbleKit JS ready.');

  // Update s_js_ready on watch
  Pebble.sendAppMessage({'JSReady': 1});
});
```

This key should be interpreted in the app's ``AppMessageInboxReceived``
implementation:

```c
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 messages
    s_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:

```c
static AppTimer *s_timeout_timer;
```

When the message is sent, the timer should be scheduled:

```c
static void send_with_timeout(int key, int value) {
  // Construct and send the message
  DitionaryIterator *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 timer
  const int interval_ms = 1000;
  s_timout_timer = app_timer_register(interval_ms, timout_timer_handler, NULL);
}
```

If the ``AppMessageOutboxSent`` is called, the message was a success, and the
timer should be cancelled:

```c
static void outbox_sent_handler(DictionaryIterator *iter, void *context) {
  // Successful message, the timeout is not needed anymore for this message
  app_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:

```c
static void timout_timer_handler(void *context) {
  // The timer elapsed because no success was reported
  text_layer_set_text(s_status_layer, "Failed. Retrying...");

  // Retry the message
  send_with_timeout(some_key, some_value);
}
```

Alternatively, if the ``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:

```c
static void outbox_failed_handler(DictionaryIterator *iter,
                                      AppMessageResult reason, void *context) {
  // Message failed before timer elapsed, reschedule for later
  if(s_timout_timer) {
    app_timer_cancel(s_timout_timer);
  }

  // Inform the user of the failure
  text_layer_set_text(s_status_layer, "Failed. Retrying...");

  // Use the timeout handler to perform the same action - resend the message
  const 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`` 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`` API.

### Sending a List to the Phone

For instance, the ``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:

```c
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:

```c
static int s_index = 0;
```

When a message has been sent, this index is used to construct the next message:

{% markdown %}
> 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]`
{% endmarkdown %}

{% markdown %}
> 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 the project's
> 'Settings' page, like so: `someArray[6]`
{% endmarkdown %}

```c
static void outbox_sent_handler(DictionaryIterator *iter, void *context) {
  // Increment the index
  s_index++;

  if(s_index < NUM_ITEMS) {
    // Send the next item
    DictionaryIterator *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 sequence
    APP_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:

```js
var NUM_ITEMS = 6;
var keys = require('message_keys');

var data = [];
var index = 0;

Pebble.addEventListener('appmessage', function(e) {
  // Store this data item
  data[index] = e.payload[keys.someArray + index];

  // Increment index for next message
  index++;

  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:

```js
var keys = require('message_keys');
function sendNextItem(items, index) {
  // Build message
  var key = keys.someArray + index;
  var dict = {};
  dict[key] = items[index];

  // Send the message
  Pebble.sendAppMessage(dict, function() {
    // Use success callback to increment index
    index++;

    if(index < items.length) {
      // Send next item
      sendNextItem(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 array
  var json = JSON.parse(responseText);

  // Begin transmission loop
  sendList(json.items);
}
```

On the watchapp side, the items are received in the same order in the
``AppMessageInboxReceived`` handler:

```c
#define NUM_ITEMS 6

static int s_index;
static int s_data[NUM_ITEMS];
```

```c
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 item
    s_data[index] = (int)data_t->value->int32;

    // Increment index for next item
    s_index++;
  }

  if(s_index == NUM_ITEMS) {
    // We have reached the end of the sequence
    APP_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()``. 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``'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/):

![](http://developer.getpebble.com.s3.amazonaws.com/assets/other/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:

```js
function downloadImage() {
  var url = 'http://developer.getpebble.com.s3.amazonaws.com/assets/other/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):

```js
function processImage(responseData) {
  // Convert to a array
  var byteArray = new Uint8Array(responseData);
  var array = [];
  for(var i = 0; i < byteArray.byteLength; i++) {
    array.push(byteArray[i]);
  }

  // Send chunks to Pebble
  transmitImage(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:

```js
function transmitImage(array) {
  var index = 0;
  var arrayLength = array.length;

  // Transmit the length for array allocation
  Pebble.sendAppMessage({'DataLength': arrayLength}, function(e) {
    // Success, begin sending chunks
    sendChunk(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:

```js
function sendChunk(array, index, arrayLength) {
  // Determine the next chunk size
  var chunkSize = BUFFER_SIZE;
  if(arrayLength - index < BUFFER_SIZE) {
    // Resize to fit just the remaining data items
    chunkSize = arrayLength - index;
  }

  // Prepare the dictionary
  var dict = {
    'DataChunk': array.slice(index, index + chunkSize),
    'ChunkSize': chunkSize,
    'Index': index
  };

  // Send the chunk
  Pebble.sendAppMessage(dict, function() {
    // Success
    index += chunkSize;

    if(index < arrayLength) {
      // Send the next chunk
      sendChunk(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`` 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()``) and the buffer
used to store the chunks is allocated to this exact size:

```c
static uint8_t *s_img_data;
static int s_img_size;
```

```c
// Get the received image chunk
Tuple *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 data
  img_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:

```c
// An image chunk
Tuple *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 chunk
  memcpy(&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``:

```c
static BitmapLayer *s_bitmap_layer;
static GBitmap *s_bitmap;
```

```c
// Complete?
Tuple *complete_t = dict_find(iter, MESSAGE_KEY_Complete);
if(complete_t) {
  // Create new GBitmap from downloaded PNG data
  s_bitmap = gbitmap_create_from_png_data(s_img_data, s_img_size);

  // Show the image
  if(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.

Get the complete source code for this example from the
[`png-download-example`](https://github.com/pebble-examples/png-download-example)
repository on GitHub.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Datalogging
description: |
  Information on how to collect data batches using the Datalogging API.
guide_group: communication
order: 1
related_examples:
  - title: Tricorder
  - url: https://github.com/pebble-examples/tricorder
---

In addition to the more realtime ``AppMessage`` API, the Pebble SDK also
includes the ``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
{% guide_link communication/using-pebblekit-android %} or
{% guide_link communication/using-pebblekit-ios %} companion
app that wishes to process it.

## Collecting Data

Datalogging can capture any values that are compatible with one of the
``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:

```c
// 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:

```c
// The session reference variable
static DataLoggingSessionRef s_session_ref;
```

```c
static void init() {
  // Begin the session
  s_session_ref = data_logging_create(TIMESTAMP_LOG, DATA_LOGGING_INT, sizeof(int), true);

  /* ... */
}
```

> Note: The final parameter of ``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()`` will add a new entry to the log indicated by the
``DataLoggingSessionRef`` variable provided. The success of each logging
operation can be checked using the ``DataLoggingResult`` returned:

```c
const int value = 16;
const uint32_t num_values = 1;

// Log a single value
DataLoggingResult result = data_logging_log(s_session_ref, &value, num_values);

// Was the value successfully stored? If it failed, print the reason
if(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.

```c
// Finish the session and sync data if appropriate
data_logging_finish(s_session_ref);
```

> Note: Once a session has been finished, data cannot be logged to its
> ``DataLoggingSessionRef`` until it is resumed or began anew.

## Receiving Data

> Note: Datalogging data cannot be received via PebbleKit JS.

Data collected with the ``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:

```java
// The UUID of the watchapp
private 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`:

```java
// Create a receiver to collect logged data
PebbleKit.PebbleDataLogReceiver dataLogReceiver =
        new PebbleKit.PebbleDataLogReceiver(APP_UUID) {

  @Override
  public 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 text
    String current = dataView.getText().toString();
    current += timestamp.toString() + ": " + data
                + "s since watchapp launch.\n";
    dataView.setText(current);
  }

  @Override
  public void onFinishSession(Context context, UUID logUuid, Long timestamp,
                                                                    Long tag) {
    Log.i(TAG, "Session " + tag + " finished!");
  }

};

// Register the receiver
PebbleKit.registerDataLogReceiver(getApplicationContext(), dataLogReceiver);
```

<div class="alert alert--fg-white alert--bg-dark-red">
{% markdown %}
**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.
{% endmarkdown %}
</div>

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:

```java
@Override
protected 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`` (see
{% guide_link communication/using-pebblekit-ios %} for details),
simply register the class as a datalogging delegate:

```objective-c
// 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](/docs/pebblekit-ios/Protocols/PBDataLoggingServiceDelegate/) for when new data
is available, and when the session has been finished by the watch. Implement
these callbacks to read the new data:

```objective-c
- (BOOL)dataLoggingService:(PBDataLoggingService *)service
              hasSInt32s:(const SInt32 [])data
           numberOfItems:(UInt16)numberOfItems
              forDataLog:(PBDataLoggingSessionMetadata *)log {
  NSLog(@"New data received!");

  // Append newest data to displayed string
  NSString *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 *)service
            logDidFinish:(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.


---
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

title: Communication
description: |
  How to talk to the phone via PebbleKit with JavaScript and on Android or iOS.
guide_group: communication
menu: false
permalink: /guides/communication/
generate_toc: false
hide_comments: true
platform_choice: true
---

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

{% include guides/contents-group.md group=page.group_data %}

## 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`` API.

Depending on the requirements of the app, there are three possible ways to
receive data sent from Pebble on the connected phone:

* {% guide_link communication/using-pebblekit-js %} - A JavaScript
  environment running within the official Pebble mobile app with web,
  geolocation, and extended storage access.

* {% guide_link communication/using-pebblekit-android %} -
  A library available to use in Android companion apps that allows them to
  interact with standard Android platform APIs.

* {% guide_link communication/using-pebblekit-ios %} -
  As above, but for iOS companion apps.

{% markdown %}
**Important**

PebbleKit JS cannot be used in conjunction with PebbleKit Android or PebbleKit
iOS.
{% endmarkdown %}

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:

{% markdown %}
* If at least an `index.js` file is present in `src/pkjs/`, the message will be
  handled by PebbleKit JS.
{% endmarkdown %}

{% markdown %}
* If the project contains at least one JavaScript file, the message will be
  handled by PebbleKit JS.
{% endmarkdown %}

* 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.