---
# 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: Sending and Receiving Data
description: |
  How to send and receive data between your watchapp and phone.
guide_group: communication
order: 5
---

Before using ``AppMessage``, a Pebble C app must set up the buffers used for the
inbox and outbox. These are used to store received messages that have not yet
been processed, and sent messages that have not yet been transmitted. In
addition, callbacks may be registered to allow an app to respond to any success
or failure events that occur when dealing with messages. Doing all of this is
discussed in this guide.

## Message Structure

Every message sent or received using the ``AppMessage`` API is stored in a
``DictionaryIterator`` structure, which is essentially a list of ``Tuple``
objects. Each ``Tuple`` contains a key used to 'label' the value associated with
that key.

When a message is sent, a ``DictionaryIterator`` is filled with a ``Tuple`` for
each item of outgoing data. Conversely, when a message is received the
``DictionaryIterator`` provided by the callback is examined for the presence of
each key. If a key is present, the value associated with it can be read.

## Data Types

The [`Tuple.value`](``Tuple``) union allows multiple data types to be stored in
and read from each received message. These are detailed below:

| Name | Type | Size in Bytes | Signed? |
|------|------|---------------|---------|
| uint8 | `uint8_t` | 1 | No |
| uint16 | `uint16_t` | 2 | No |
| uint32 | `uint32_t` | 4 | No |
| int8 | `int8_t` | 1 | Yes |
| int16 | `int16_t` | 2 | Yes |
| int32 | `int32_t` | 4 | Yes |
| cstring | `char[]` | Variable length array | N/A |
| data | `uint8_t[]` | Variable length array | N/A |

## Buffer Sizes

The size of each of the outbox and inbox buffers must be set chosen such that
the largest message that the app will ever send or receive will fit. Incoming
messages that exceed the size of the inbox buffer, and outgoing messages that
exceed that size of the outbox buffer will be dropped.

These sizes are specified when the ``AppMessage`` system is 'opened', allowing
communication to occur:

```c
// Largest expected inbox and outbox message sizes
const uint32_t inbox_size = 64;
const uint32_t outbox_size = 256;

// Open AppMessage
app_message_open(inbox_size, outbox_size);
```

Each of these buffers is allocated at this moment, and comes out of the app's
memory budget, so the sizes of the inbox and outbox should be conservative.
Calculate the size of the buffer you require by summing the sizes of all the
keys and values in the larges message the app will handle. For example, a
message containing three integer keys and values will work with a 32 byte buffer
size.

## Choosing Keys

For each message sent and received, the contents are accessible using keys-value
pairs in a ``Tuple``. This allows each piece of data in the message to be
uniquely identifiable using its key, and also allows many different data types
to be stored inside a single message.

Each possible piece of data that may be transmitted should be assigned a unique
key value, used to read the associated value when it is found in a received
message. An example for a weather app is shown below::

* Temperature
* WindSpeed
* WindDirection
* RequestData
* LocationName

These values will be made available in any file that includes `pebble.h` prefixed
with `MESSAGE_KEY_`, such as `MESSAGE_KEY_Temperature` and `MESSAGE_KEY_WindSpeed`.

Examples of how these key values would be used in the phone-side app are
shown in {% guide_link communication/using-pebblekit-js %},
{% guide_link communication/using-pebblekit-ios %}, and
{% guide_link communication/using-pebblekit-android %}.

## Using Callbacks

Like many other aspects of the Pebble C API, the ``AppMessage`` system makes
use of developer-defined callbacks to allow an app to gracefully handle all
events that may occur, such as successfully sent or received messages as well as
any errors that may occur.

These callback types are discussed below. Each is used by first creating a
function that matches the signature of the callback type, and then registering
it with the ``AppMessage`` system to be called when that event type occurs. Good
use of callbacks to drive the app's UI will result in an improved user
experience, especially when errors occur that the user can be guided in fixing.

### Inbox Received

The ``AppMessageInboxReceived`` callback is called when a new message has been
received from the connected phone. This is the moment when the contents can be
read and used to drive what the app does next, using the provided
``DictionaryIterator`` to read the message. An example is shown below under
[*Reading an Incoming Message*](#reading-an-incoming-message):

```c
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
  // A new message has been successfully received

}
```

Register this callback so that it is called at the appropriate time:

```c
// Register to be notified about inbox received events
app_message_register_inbox_received(inbox_received_callback);
```

### Inbox Dropped

The ``AppMessageInboxDropped`` callback is called when a message was received,
but it was dropped. A common cause of this is that the message was too big for
the inbox. The reason for failure can be determined using the
``AppMessageResult`` provided by the callback:

```c
static void inbox_dropped_callback(AppMessageResult reason, void *context) {
  // A message was received, but had to be dropped
  APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped. Reason: %d", (int)reason);
}
```

Register this callback so that it is called at the appropriate time:

```c
// Register to be notified about inbox dropped events
app_message_register_inbox_dropped(inbox_dropped_callback);
```

### Outbox Sent

The ``AppMessageOutboxSent`` callback is called when a message sent from Pebble
has been successfully delivered to the connected phone. The provided
``DictionaryIterator`` can be optionally used to inspect the contents of the
message just sent.

> When sending multiple messages in a short space of time, it is **strongly**
> recommended to make use of this callback to wait until the previous message
> has been sent before sending the next.

```c
static void outbox_sent_callback(DictionaryIterator *iter, void *context) {
  // The message just sent has been successfully delivered

}
```

Register this callback so that it is called at the appropriate time:

```c
// Register to be notified about outbox sent events
app_message_register_outbox_sent(outbox_sent_callback);
```

### Outbox Failed

The ``AppMessageOutboxFailed`` callback is called when a message just sent
failed to be successfully delivered to the connected phone. The reason can be
determined by reading the value of the provided ``AppMessageResult``, and the
contents of the failed message inspected with the provided
``DictionaryIterator``.

Use of this callback is strongly encouraged, since it allows an app to detect a
failed message and either retry its transmission, or inform the user of the
failure so that they can attempt their action again.

```c
static void outbox_failed_callback(DictionaryIterator *iter,
                                      AppMessageResult reason, void *context) {
  // The message just sent failed to be delivered
  APP_LOG(APP_LOG_LEVEL_ERROR, "Message send failed. Reason: %d", (int)reason);
}
```

Register this callback so that it is called at the appropriate time:

```c
// Register to be notified about outbox failed events
app_message_register_outbox_failed(outbox_failed_callback);
```

## Constructing an Outgoing Message

A message is constructed and sent from the C app via ``AppMessage`` using a
``DictionaryIterator`` object and the ``Dictionary`` APIs. Ensure that
``app_message_open()`` has been called before sending or receiving any messages.

The first step is to begin an outgoing message by preparing a
``DictionaryIterator`` pointer, used to keep track of the state of the
dictionary being constructed:

```c
// Declare the dictionary's iterator
DictionaryIterator *out_iter;

// Prepare the outbox buffer for this message
AppMessageResult result = app_message_outbox_begin(&out_iter);
```

The ``AppMessageResult`` should be checked to make sure the outbox was
successfully prepared:

```c
if(result == APP_MSG_OK) {
  // Construct the message

} else {
  // The outbox cannot be used right now
  APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result);
}
```

If the result is ``APP_MSG_OK``, the message construction can continue. Data is
now written to the dictionary according to data type using the ``Dictionary``
APIs. An example from the hypothetical weather app is shown below:

```c
if(result == APP_MSG_OK) {
  // A dummy value
  int value = 0;

  // Add an item to ask for weather data
  dict_write_int(out_iter, MESSAGE_KEY_RequestData, &value, sizeof(int), true);
}
```

After all desired data has been written to the dictionary, the message may be
sent:

```c
// Send this message
result = app_message_outbox_send();

// Check the result
if(result != APP_MSG_OK) {
  APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result);
}
```

<div class="alert alert--fg-white alert--bg-dark-red">
{% markdown %}
**Important**

Any app that wishes to send data from the watch to the phone via PebbleKit JS
must wait until the `ready` event has occured, indicating that the phone has
loaded the JavaScript for the app and it is ready to receive data. See
[*Advanced Communication*](/guides/communication/advanced-communication#waiting-for-pebblekit-js)
for more information.
{% endmarkdown %}
</div>

Once the message send operation has been completed, either the
``AppMessageOutboxSent`` or ``AppMessageOutboxFailed`` callbacks will be called
(if they have been registered), depending on either a success or failure
outcome.

### Example Outgoing Message Construction

A complete example of assembling an outgoing message is shown below:

```c
// Declare the dictionary's iterator
DictionaryIterator *out_iter;

// Prepare the outbox buffer for this message
AppMessageResult result = app_message_outbox_begin(&out_iter);
if(result == APP_MSG_OK) {
  // Add an item to ask for weather data
  int value = 0;
  dict_write_int(out_iter, MESSAGE_KEY_RequestData, &value, sizeof(int), true);

  // Send this message
  result = app_message_outbox_send();
  if(result != APP_MSG_OK) {
    APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result);
  }
} else {
  // The outbox cannot be used right now
  APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result);
}
```

## Reading an Incoming Message

When a message is received from the connected phone the
``AppMessageInboxReceived`` callback is called, and the message's contents can
be read using the provided ``DictionaryIterator``. This should be done by
looking for the presence of each expectd `Tuple` key value, and using the
associated value as required.

Most apps will deal with integer values or strings to pass signals or some
human-readable information respectively. These common use cases are discussed
below.

### Reading an Integer

**From JS**

```js
var dict  = {
  'Temperature': 29
};
```

**In C**

```c
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
  // A new message has been successfully received

  // Does this message contain a temperature value?
  Tuple *temperature_tuple = dict_find(iter, MESSAGE_KEY_Temperature);
  if(temperature_tuple) {
    // This value was stored as JS Number, which is stored here as int32_t
    int32_t temperature = temperature_tuple->value->int32;
  }
}
```

### Reading a String

A common use of transmitted strings is to display them in a ``TextLayer``. Since
the displayed text is required to be long-lived, a `static` `char` buffer can be
used when the data is received:

**From JS**

```js
var dict = {
  'LocationName': 'London, UK'
};
```

**In C**

```c
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
  // Is the location name inside this message?
  Tuple *location_tuple = dict_find(iter, MESSAGE_KEY_LocationName);
  if(location_tuple) {
    // This value was stored as JS String, which is stored here as a char string
    char *location_name = location_tuple->value->cstring;

    // Use a static buffer to store the string for display
    static char s_buffer[MAX_LENGTH];
    snprintf(s_buffer, sizeof(s_buffer), "Location: %s", location_name);

    // Display in the TextLayer
    text_layer_set_text(s_text_layer, s_buffer);
  }
}
```

### Reading Binary Data

Apps that deal in packed binary data can send this data and pack/unpack as
required on either side:

**From JS**

```js
var dict = {
  'Data': [1, 2, 4, 8, 16, 32, 64]
};
```

**In C**

```c
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
  // Expected length of the binary data
  const int length = 32;

  // Does this message contain the data tuple?
  Tuple *data_tuple = dict_find(iter, MESSAGE_KEY_Data);
  if(data_tuple) {
    // Read the binary data value
    uint8_t *data = data_tuple->value->data;

    // Inspect the first byte, for example
    uint8_t byte_zero = data[0];

    // Store into an app-defined buffer
    memcpy(s_buffer, data, length);
  }
```


---
# 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: PebbleKit Android
description: How to use PebbleKit to communicate with a watchapp on Android.
guide_group: communication
order: 2
---

[PebbleKit Android](https://github.com/pebble/pebble-android-sdk/) is a Java
library that works with the Pebble SDK and can be embedded in any Android
application. Using the classes and methods in this library, an Android companion
app can find and exchange data with a Pebble watch.

This section assumes that the reader is familiar with basic Android development
and Android Studio as an integrated development environment. Refer to the
[Android Documentation](http://developer.android.com/sdk/index.html) for more
information on installing the Android SDK.

Most PebbleKit Android methods require a `Context` parameter. An app can use
`getApplicationContext()`, which is available from any `Activity`
implementation.

### Setting Up PebbleKit Android

Add PebbleKit Android to an Android Studio project in the
`app/build.gradle` file:

```
dependencies {
  compile 'com.getpebble:pebblekit:{{ site.data.sdk.pebblekit-android.version }}'
}
```

### Sending Messages from Android

Since Android apps are built separately from their companion Pebble apps, there is
no way for the build system to automatically create matching appmessage keys.
You must therefore manually specify them in `package.json`, like so:

```js
{
  "ContactName": 0,
  "Age": 1
}
```

These numeric values can then be used as appmessage keys in your Android app.

Messages are constructed with the `PebbleDictionary` class and sent to a C
watchapp or watchface using the `PebbleKit` class. The first step is to create a
`PebbleDictionary` object:

```java
// Create a new dictionary
PebbleDictionary dict = new PebbleDictionary();
```

Data items are added to the
[`PebbleDictionary`](/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary)
using key-value pairs with the methods made available by the object, such as
`addString()` and `addInt32()`. An example is shown below:

```java
// The key representing a contact name is being transmitted
final int AppKeyContactName = 0;
final int AppKeyAge = 1;

// Get data from the app
final String contactName = getContact();
final int age = getAge();

// Add data to the dictionary
dict.addString(AppKeyContactName, contactName);
dict.addInt32(AppKeyAge, age);
```

Finally, the dictionary is sent to the C app by calling `sendDataToPebble()`
with a UUID matching that of the C app that will receive the data:

```java
final UUID appUuid = UUID.fromString("EC7EE5C6-8DDF-4089-AA84-C3396A11CC95");

// Send the dictionary
PebbleKit.sendDataToPebble(getApplicationContext(), appUuid, dict);
```

Once delivered, this dictionary will be available in the C app via the
``AppMessageInboxReceived`` callback, as detailed in
{% guide_link communication/sending-and-receiving-data#inbox-received %}.

### Receiving Messages on Android

Receiving messages from Pebble in a PebbleKit Android app requires a listener to
be registered in the form of a `PebbleDataReceiver` object, which extends
`BroadcastReceiver`:

```java
// Create a new receiver to get AppMessages from the C app
PebbleDataReceiver dataReceiver = new PebbleDataReceiver(appUuid) {

  @Override
  public void receiveData(Context context, int transaction_id,
                                                    PebbleDictionary dict) {
    // A new AppMessage was received, tell Pebble
    PebbleKit.sendAckToPebble(context, transaction_id);
  }

};
```

<div class="alert alert--fg-white alert--bg-dark-red">
{% markdown %}
**Important**

PebbleKit apps **must** manually send an acknowledgement (Ack) to Pebble to
inform it that the message was received successfully. Failure to do this will
cause timeouts.
{% endmarkdown %}
</div>

Once created, this receiver should be registered in `onResume()`, overridden
from `Activity`:

```java
@Override
public void onResume() {
  super.onResume();

  // Register the receiver
  PebbleKit.registerReceivedDataHandler(getApplicationContext(), dataReceiver);
}
```

> Note: To avoid getting callbacks after the `Activity` or `Service` has exited,
> apps should attempt to unregister the receiver in `onPause()` with
> `unregisterReceiver()`.

With a receiver in place, data can be read from the provided
[`PebbleDictionary`](/docs/pebblekit-android/com/getpebble/android/kit/util/PebbleDictionary)
using analogous methods such as `getString()` and `getInteger()`. Before reading
the value of a key, the app should first check that it exists using a `!= null`
check.

The example shown below shows how to read an integer from the message, in the
scenario that the watch is sending an age value to the Android companion app:

```java
@Override
public void receiveData(Context context, int transaction_id,
                                                      PebbleDictionary dict) {
  // If the tuple is present...
  Long ageValue = dict.getInteger(AppKeyAge);
  if(ageValue != null) {
    // Read the integer value
    int age = ageValue.intValue();
  }
}
```

### Other Capabilities

In addition to sending and receiving messages, PebbleKit Android also allows
more intricate interactions with Pebble. See the
[PebbleKit Android Documentation](/docs/pebblekit-android/com/getpebble/android/kit/PebbleKit/)
for a complete list of available methods. Some examples are shown below of what
is possible:

* Checking if the watch is connected:

    ```java
    boolean connected = PebbleKit.isWatchConnected(getApplicationContext());
    ```

* Registering for connection events with `registerPebbleConnectedReceiver()` and
  `registerPebbleDisconnectedReceiver()`, and a suitable `BroadcastReceiver`.

    ```java
    PebbleKit.registerPebbleConnectedReceiver(getApplicationContext(),
                                                      new BroadcastReceiver() {

      @Override
      public void onReceive(Context context, Intent intent) { }

    });
    ```

* Registering for Ack/Nack events with `registerReceivedAckHandler()` and
  `registerReceivedNackHandler()`.

    ```java
    PebbleKit.registerReceivedAckHandler(getApplicationContext(),
                                  new PebbleKit.PebbleAckReceiver(appUuid) {

      @Override
      public void receiveAck(Context context, int i) { }

    });
    ```

* Launching and killing the watchapp with `startAppOnPebble()` and
  `closeAppOnPebble()`.

    ```java
    PebbleKit.startAppOnPebble(getApplicationContext(), appUuid);
    ```

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: PebbleKit iOS
description: How to use PebbleKit to communicate with a watchapp on iOS.
guide_group: communication
order: 3
---

[PebbleKit iOS](https://github.com/pebble/pebble-ios-sdk/) is an Objective-C
framework that works with the Pebble SDK and can be embedded in any iOS
application for **iOS 7.1** and above. Using the classes and methods in this
framework, an iOS app can find and exchange data with a Pebble watch.

This section assumes that the reader has a basic knowledge of Objective-C, Xcode
as an IDE, and the delegate and block patterns.

> PebbleKit iOS should be compatible if your app uses Swift. The framework
> itself is written in Objective-C to avoid the requirement of the Swift runtime
> in pure Objective-C apps, and to improve the backwards and forwards
> compatibility.

### Setting Up PebbleKit iOS

If the project is using [CocoaPods](http://cocoapods.org/) (which is the
recommended approach), just add `pod 'PebbleKit'` to the `Podfile` and execute
`pod install`.

After installing PebbleKit iOS in the project, perform these final steps:

* If the iOS app needs to run in the background, you should update your target’s
  “Capabilities” in Xcode. Enable “Background Modes” and select both “Uses
  Bluetooth LE accessories” and “Acts as a Bluetooth LE accessory”. This should
  add the keys `bluetooth-peripheral` (“App shares data using CoreBluetooth”)
  and `bluetooth-central` (“App communicates using CoreBluetooth”) to your
  target’s `Info.plist` file.
* If you are using Xcode 8 or greater (and recommended for previous versions),
  you must also add the key `NSBluetoothPeripheralUsageDescription` (“Privacy -
  Bluetooth Peripheral Usage Description”) to your `Info.plist`.

> To add PebbleKit iOS manually, or some other alternatives follow the steps in
> the [repository](https://github.com/pebble/pebble-ios-sdk/). The documentation
> might also include more information that might be useful. Read it carefully.

### Targeting a Companion App

Before an iOS companion app can start communicating or exchange messages with a
watchapp on Pebble, it needs to give PebbleKit a way to identify the watchapp.
The UUID of your watchapp is used for this purpose.

Set the app UUID associated with the PBPebbleCentral instance. A simple way to
create a UUID in standard representation to `NSUUID` is shown here:

```objective-c
// Set UUID of watchapp
NSUUID *myAppUUID =
    [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"];
[PBPebbleCentral defaultCentral].appUUID = myAppUUID;
```

If you are trying to communicate with the built-in Sports or Golf apps, their
UUID are available as part of PebbleKit with ``PBSportsUUID`` and
``PBGolfUUID``. You must register those UUID if you intend to communicate with
those apps.

### Becoming a Delegate

To communicate with a Pebble watch, the class must implement
`PBPebbleCentralDelegate`:

```objective-c
@interface ViewController () <PBPebbleCentralDelegate>
```

The `PBPebbleCentral` class should not be instantiated directly. Instead, always
use the singleton provided by `[PBPebbleCentral defaultCentral]`. An example is
shown below, with the Golf app UUID:

```objective-c
central = [PBPebbleCentral defaultCentral];
central.appUUID = myAppUUID;
[central run];
```

Once this is done, set the class to be the delegate:

```objective-c
[PBPebbleCentral defaultCentral].delegate = self;
```

This delegate will get two callbacks: `pebbleCentral:watchDidConnect:isNew:` and
`pebbleCentral:watchDidDisconnect:` every time a Pebble connects or disconnects.
The app won't get connection callbacks if the Pebble is already connected when
the delegate is set.

Implement these to receive the associated connection/disconnection events:

```objective-c
- (void)pebbleCentral:(PBPebbleCentral *)central watchDidConnect:(PBWatch *)watch isNew:(BOOL)isNew {
  NSLog(@"Pebble connected: %@", watch.name);

  // Keep a reference to this watch
  self.connectedWatch = watch;
}

- (void)pebbleCentral:(PBPebbleCentral *)central watchDidDisconnect:(PBWatch *)watch {
  NSLog(@"Pebble disconnected: %@", watch.name);

  // If this was the recently connected watch, forget it
  if ([watch isEqual:self.connectedWatch]) {
    self.connectedWatch = nil;
  }
}
```

### Initiating Bluetooth Communication

Once the iOS app is correctly set up to communicate with Pebble, the final step
is to actually begin communication. No communication can take place until the
following is called:

```objective-c
[[PBPebbleCentral defaultCentral] run];
```

> Once this occurs, the user _may_ be shown a dialog asking for confirmation
> that they want the app to communicate. This means the app should not call
> `run:` until the appropriate moment in the UI.

### Sending Messages from iOS

Since iOS apps are built separately from their companion Pebble apps, there is
no way for the build system to automatically create matching app message keys.
You must therefore manually specify them in `package.json`, like so:

```js
{
  "Temperature": 0,
  "WindSpeed": 1,
  "WindDirection": 2,
  "RequestData": 3,
  "LocationName": 4
}
```

These numeric values can then be used as app message keys in your iOS app.

Messages are constructed with the `NSDictionary` class and sent to the C
watchapp or watchface by the `PBPebbleCentralDelegate` when the
`appMessagesPushUpdate:` function is invoked.

To send a message, prepare an `NSDictionary` object with the data to be sent to
the C watchapp. Data items are added to the
[`NSDictionary`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/)
using key-value pairs of standard data types. An example containing a string and
an integer is shown below:

```objective-c
NSDictionary *update = @{ @(0):[NSNumber pb_numberWithUint8:42],
                          @(1):@"a string" };
```

Send this dictionary to the watchapp using `appMessagesPushUpdate:`. The first
argument is the update dictionary to send and the second argument is a callback
block that will be invoked when the data has been acknowledged by the watch (or
if an error occurs).

```objective-c
[self.connectedWatch appMessagesPushUpdate:update onSent:^(PBWatch *watch, NSDictionary *update, NSError *error) {
  if (!error) {
    NSLog(@"Successfully sent message.");
  } else {
    NSLog(@"Error sending message: %@", error);
  }
}];
```

Once delivered, this dictionary will be available in the C app via the
``AppMessageInboxReceived`` callback, as detailed in
{% guide_link communication/sending-and-receiving-data#inbox-received %}.

### Receiving Messages on iOS

To receive messages from a watchapp, register a receive handler (a block)
with `appMessagesAddReceiveUpdateHandler:`. This block will be invoked with two
parameters - a pointer to a `PBWatch` object describing the Pebble that sent the
message and an `NSDictionary` with the message received.

```objective-c
[self.connectedWatch appMessagesAddReceiveUpdateHandler:^BOOL(PBWatch *watch, NSDictionary *update) {
    NSLog(@"Received message: %@", update);

    // Send Ack to Pebble
    return YES;
}];
```

> Always return `YES` in the handler. This instructs PebbleKit to automatically
> send an ACK to Pebble, to avoid the message timing out.

Data can be read from the `NSDictionary` by first testing for each key's
presence using a `!= nil` check, and reading the value if it is present:

```objective-c
NSNumber *key = @1;

// If the key is present in the received dictionary
if (update[key]) {
  // Read the integer value
  int value = [update[key] intValue];
}
```

### Other Capabilities

In addition to sending and receiving messages, PebbleKit iOS also allows
more intricate interactions with Pebble. See the
[PebbleKit iOS Documentation](/docs/pebblekit-ios/)
for more information. Some examples are shown below of what is possible:

* Checking if the watch is connected using the `connected` property of a
  `PBWatch`.

  ```objective-c
  BOOL isConnected = self.watch.connected;
  ```

* Receiving `watchDidConnect` and `watchDidDisconnect` events through being a
  `PBDataloggingServiceDelegate`.

### Limitations of PebbleKit on iOS

The iOS platform imposes some restrictions on what apps can do with accessories.
It also limits the capabilities of apps that are in the background. It is
critical to understand these limitations when developing an app that relies on
PebbleKit iOS.

On iOS, all communication between a mobile app and Pebble is managed through a
communication session. This communication session is a protocol specific to iOS,
with notable limitations that the reader should know and understand when
developing an iOS companion app for Pebble.

#### Bluetooth Low Energy (BLE) Connections

For Pebble apps that communicate with Pebble in BLE mode, a session can be
created for each app that requires one. This removes the 'one session only'
restriction, but only for these BLE apps. Currently, there are several
BLE only devices, such as Pebble Time Round, and Pebble 2, but all the devices
using a firmware 3.8 or greater can use BLE to communicate with PebbleKit.

For BLE apps, the 'phone must launch' restriction is removed. The iOS
companion app can be restarted by the watchapp if it stops working if user
force-quits iOS app, or it crashes. Note that the app will not work after
rebooting iOS device, which requires it be launched by the iPhone user once
after boot.

#### Communication with firmware older than 3.0

PebbleKit iOS 3.1.1 is the last PebbleKit that supports communication with
firmwares older than 3.0. PebbleKit iOS 4.0.0 can only communicate with Pebble
devices with firmware newer than 3.0.

For newer devices like Pebble Time, Pebble Time Steel, Pebble Time Round, and
Pebble 2 there should be no problem. For previous generation devices like Pebble
and Pebble Steel it means that their users should upgrade their firmware to the
latest firmware available for their devices using the new apps.

This change allows better compatibility and new features to be developed by 3rd
parties.


---
# 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: PebbleKit JS
description: |
  How to use PebbleKit JS to communicate with the connected phone's JS
  environment.
guide_group: communication
order: 4
platform_choice: true
---

PebbleKit JS allows a JavaScript component (run in a sandbox inside the official
Pebble mobile app) to be added to any watchapp or watchface in order to extend
the functionality of the app beyond what can be accomplished on the watch
itself.

Extra features available to an app using PebbleKit JS include:

* Access to extended storage with [`localStorage`](#using-localstorage).

* Internet access using [`XMLHttpRequest`](#using-xmlhttprequest).

* Location data using [`geolocation`](#using-geolocation).

* The ability to show a configuration page to allow users to customize how the
  app behaves. This is discussed in detail in
  {% guide_link user-interfaces/app-configuration %}.

## Setting Up

^LC^ PebbleKit JS can be set up by creating the `index.js` file in the project's
`src/pkjs/` directory. Code in this file will be executed when the associated
watchapp is launched, and will stop once that app exits.

^CP^ PebbleKit JS can be set up by clicking 'Add New' in the Source Files
section of the sidebar. Choose the 'JavaScript file' type and choose a file name
before clicking 'Create'. Code in this file will be executed when the associated
watchapp is launched, and will stop once that app exits.

The basic JS code required to begin using PebbleKit JS is shown below. An event
listener is created to listen for the `ready` event - fired when the watchapp
has been launched and the JS environment is ready to receive messages. This
callback must return within a short space of time (a few seconds) or else it
will timeout and be killed by the phone.

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

{% markdown %}
**Important**

A watchapp or watchface **must** wait for the `ready` event before attempting to
send messages to the connected phone. See
[*Advanced Communication*](/guides/communication/advanced-communication#waiting-for-pebblekit-js)
to learn how to do this.
{% endmarkdown %}

## Defining Keys

^LC^ Before any messages can be sent or received, the keys to be used to store the
data items in the dictionary must be declared. The watchapp side uses
exclusively integer keys, whereas the JavaScript side may use the same integers
or named string keys declared in `package.json`. Any string key not declared
beforehand will not be transmitted to/from Pebble.

^CP^ Before any messages can be sent or received, the keys to be used to store
the data items in the dictionary must be declared. The watchapp side uses
exclusively integer keys, whereas the JavaScript side may use the same integers
or named string keys declared in the 'PebbleKit JS Message Keys' section of
'Settings'. Any string key not declared beforehand will not be transmitted
to/from Pebble.

> Note: This requirement is true of PebbleKit JS **only**, and not PebbleKit
> Android or iOS.

^LC^  Keys are declared in the project's `package.json` file in the `messageKeys`
object, which is inside the `pebble` object. Example keys are shown as equivalents
to the ones used in the hypothetical weather app example in
{% guide_link communication/sending-and-receiving-data#choosing-key-values %}.

{% highlight {} %}
"messageKeys": [
  "Temperature",
  "WindSpeed",
  "WindDirection",
  "RequestData",
  "LocationName"
]
{% endhighlight %}

^CP^ Keys are declared individually in the 'PebbleKit JS Message Keys' section
of the 'Settings' page. Enter the 'Key Name' of each key that will be used by
the app.

The names chosen here will be injected into your C code prefixed with `MESSAGE_KEY_`,
like `MESSAGE_KEY_Temperature`. As such, they must be legal C identifiers.

If you want to emulate an array by attaching multiple "keys" to a name, you can
specify the size of the array by adding it in square brackets: for instance,
`"LapTimes[10]`" would create a key called `LapTimes` and leave nine empty keys
after it which can be accessed by arithmetic, e.g. `MESSAGE_KEY_LapTimes + 3`.

## Sending Messages from JS

Messages are sent to the C watchapp or watchface using
`Pebble.sendAppMessage()`, which accepts a standard JavaScript object containing
the keys and values to be transmitted. The keys used **must** be identical to
the ones declared earlier.

An example is shown below:

```js
// Assemble data object
var dict = {
  'Temperature': 29,
  'LocationName': 'London, UK'
};

// Send the object
Pebble.sendAppMessage(dict, function() {
  console.log('Message sent successfully: ' + JSON.stringify(dict));
}, function(e) {
  console.log('Message failed: ' + JSON.stringify(e));
});
```

It is also possible to read the numeric values of the keys by `require`ing
`message_keys`, which is necessary to use the array feature. For instance:

```js
// Require the keys' numeric values.
var keys = require('message_keys');

// Build a dictionary.
var dict = {}
dict[keys.LapTimes] = 42
dict[keys.LapTimes+1] = 51

// Send the object
Pebble.sendAppMessage(dict, function() {
  console.log('Message sent successfully: ' + JSON.stringify(dict));
}, function(e) {
  console.log('Message failed: ' + JSON.stringify(e));
});
```

### Type Conversion

Depending on the type of the item in the object to be sent, the C app will be
able to read the value (from the
[`Tuple.value` union](/guides/communication/sending-and-receiving-data#data-types))
according to the table below:

| JS Type | Union member |
|---------|--------------|
| String | cstring |
| Number | int32 |
| Array | data |
| Boolean | int16 |

## Receiving Messages in JS

When a message is received from the C watchapp or watchface, the `appmessage`
event is fired in the PebbleKit JS app. To receive these messages, register the
appropriate event listener:

```js
// Get AppMessage events
Pebble.addEventListener('appmessage', function(e) {
  // Get the dictionary from the message
  var dict = e.payload;

  console.log('Got message: ' + JSON.stringify(dict));
});
```

Data can be read from the dictionary by reading the value if it is present. A
suggested best practice involves first checking for the presence of each key
within the callback using an `if()` statement.

```js
if(dict['RequestData']) {
  // The RequestData key is present, read the value
  var value = dict['RequestData'];
}
```

## Using LocalStorage

In addition to the storage available on the watch itself through the ``Storage``
API, apps can take advantage of the larger storage on the connected phone
through the use of the HTML 5 [`localStorage`](http://www.w3.org/TR/webstorage/)
API. Data stored here will persist across app launches, and so can be used to
persist latest data, app settings, and other data.

PebbleKit JS `localStorage` is:

* Associated with the application UUID and cannot be shared between apps.

* Persisted when the user uninstalls and then reinstalls an app.

* Persisted when the user upgrades an app.

To store a value:

```js
var color = '#FF0066';

// Store some data
localStorage.setItem('backgroundColor', color);
```

To read the data back:

```js
var color = localStorage.getItem('backgroundColor');
```

> Note: Keys used with `localStorage` should be Strings.

## Using XMLHttpRequest

A PebbleKit JS-equipped app can access the internet and communicate with web
services or download data using the standard
[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
object.

To communicate with the web, create an `XMLHttpRequest` object and send it,
specifying the HTTP method and URL to be used, as well as a callback for when it
is successfully completed:

```js
var method = 'GET';
var url = 'http://example.com';

// Create the request
var request = new XMLHttpRequest();

// Specify the callback for when the request is completed
request.onload = function() {
  // The request was successfully completed!
  console.log('Got response: ' + this.responseText);
};

// Send the request
request.open(method, url);
request.send();
```

If the response is expected to be in the JSON format, data items can be easily
read after the `responseText` is converted into a JSON object:

```js
request.onload = function() {
  try {
    // Transform in to JSON
    var json = JSON.parse(this.responseText);

    // Read data
    var temperature = json.main.temp;
  } catch(err) {
    console.log('Error parsing JSON response!');
  }
};
```

## Using Geolocation

PebbleKit JS provides access to the location services provided by the phone
through the
[`navigator.geolocation`](http://dev.w3.org/geo/api/spec-source.html) object.

^CP^ Declare that the app will be using the `geolocation` API by checking the
'Uses Location' checkbox in the 'Settings' screen.

^LC^ Declare that the app will be using the `geolocation` API by adding the
string `location` in the `capabilities` array in `package.json`:

{% highlight {} %}
"capabilities": [ "location" ]
{% endhighlight %}

Below is an example showing how to get a single position value from the
`geolocation` API using the
[`getCurrentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition)
method:

```js
function success(pos) {
  console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude);
}

function error(err) {
  console.log('location error (' + err.code + '): ' + err.message);
}

/* ... */

// Choose options about the data returned
var options = {
  enableHighAccuracy: true,
  maximumAge: 10000,
  timeout: 10000
};

// Request current position
navigator.geolocation.getCurrentPosition(success, error, options);
```

Location permission is given by the user to the Pebble application for all
Pebble apps. The app should gracefully handle the `PERMISSION DENIED` error and
fallback to a default value or manual configuration when the user has denied
location access to Pebble apps.

```js
function error(err) {
  if(err.code == err.PERMISSION_DENIED) {
    console.log('Location access was denied by the user.');
  } else {
    console.log('location error (' + err.code + '): ' + err.message);
  }
}

```

The `geolocation` API also provides a mechanism to receive callbacks when the
user's position changes to avoid the need to manually poll at regular intervals.
This is achieved by using
[`watchPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition)
in a manner similar to the example below:

```js
// An ID to store to later clear the watch
var watchId;

function success(pos) {
  console.log('Location changed!');
  console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude);
}

function error(err) {
  console.log('location error (' + err.code + '): ' + err.message);
}

/* ... */

var options = {
  enableHighAccuracy: true,
  maximumAge: 0,
  timeout: 5000
};

// Get location updates
watchId = navigator.geolocation.watchPosition(success, error, options);
```

To cancel the update callbacks, use the `watchId` variable received when the
watch was registered with the
[`clearWatch()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/clearWatch)
method:

```js
// Clear the watch and stop receiving updates
navigator.geolocation.clearWatch(watchId);
```

## Account Token

PebbleKit JS provides a unique account token that is associated with the Pebble
account of the current user, accessible using `Pebble.getAccountToken()`:

```js
// Get the account token
console.log('Pebble Account Token: ' + Pebble.getAccountToken());
```

The token is a string with the following properties:

* From the developer's perspective, the account token of a user is identical
  across platforms and across all the developer's watchapps.

* If the user is not logged in, the token will be an empty string ('').

## Watch Token

PebbleKit JS also provides a unique token that can be used to identify a Pebble
device. It works in a similar way to `Pebble.getAccountToken()`:

```js
// Get the watch token
console.log('Pebble Watch Token: ' + Pebble.getWatchToken());
```

The token is a string that is unique to the app and cannot be used to track
Pebble devices across applications.

{% markdown %}
**Important**

The watch token is dependent on the watch's serial number, and therefore
**should not** be used to store sensitive user information in case the watch
changes ownership. If the app wishes to track a specific user _and_ watch, use a
combination of the watch and account token.
{% endmarkdown %}

## Showing a Notification

A PebbleKit JS app can send a notification to the watch. This uses the standard
system notification layout with customizable `title` and `body` fields:

```js
var title = 'Update Available';
var body = 'Version 1.5 of this app is now available from the appstore!';

// Show the notification
Pebble.showSimpleNotificationOnPebble(title, body);
```

> Note: PebbleKit Android/iOS applications cannot directly invoke a
> notification, and should instead leverage the respective platform notification
> APIs. These will be passed on to Pebble unless the user has turned them off in
> the mobile app.

## Getting Watch Information

Use `Pebble.getActiveWatchInfo()` to return an object of data about the
connected Pebble.

{% markdown %}
This API is currently only available for SDK 3.0 and above. Do not assume that
this function exists, so test that it is available before attempting to use it
using the code shown below.
{% endmarkdown %}

```js
var watch = Pebble.getActiveWatchInfo ? Pebble.getActiveWatchInfo() : null;

if(watch) {
  // Information is available!

} else {
  // Not available, handle gracefully

}
```

> Note: If there is no active watch available, `null` will be returned.

The table below details the fields of the returned object and the information
available.

| Field | Type | Description | Values |
|-------|------|-------------|--------|
| `platform` | String | Hardware platform name. | `aplite`, `basalt`, `chalk`. |
| `model` | String | Watch model name including color. | `pebble_black`, `pebble_grey`, `pebble_white`, `pebble_red`, `pebble_orange`, `pebble_blue`, `pebble_green`, `pebble_pink`, `pebble_steel_silver`, `pebble_steel_black`, `pebble_time_red`, `pebble_time_white`, `pebble_time_black`, `pebble_time_steel_black`, `pebble_time_steel_silver`, `pebble_time_steel_gold`, `pebble_time_round_silver_14mm`, `pebble_time_round_black_14mm`, `pebble_time_round_rose_gold_14mm`, `pebble_time_round_silver_20mm`, `pebble_time_round_black_20mm`, `qemu_platform_aplite`, `qemu_platform_basalt`, `qemu_platform_chalk`. |
| `language` | String | Language currently selected on the watch. | E.g.: `en_GB`. See the {% guide_link tools-and-resources/internationalization#locales-supported-by-pebble %} for more information. |
| `firmware` | Object | The firmware version running on the watch. | See below for sub-fields. |
| `firmware.major` | Number | Major firmware version. | E.g.: `2` |
| `firmware.minor` | Number | Minor firmware version. | E.g.: `8` |
| `firmware.patch` | Number | Patch firmware version. | E.g.: `1` |
| `firmware.suffix` | String | Any additional firmware versioning. | E.g.: `beta3` |


---
# 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: Sports API
description: |
  How to use the PebbleKit Sports API to integrate your mobile sports
  app with Pebble.
guide_group: communication
order: 6
related_examples:
  - title: PebbleKit Sports API Demos
  - url: https://github.com/pebble-examples/pebblekit-sports-api-demo
---

Every Pebble watch has two built-in system watchapps called the Sports app, and
the Golf app. These apps are hidden from the launcher until launched via
PebbleKit Android or PebbleKit iOS.

Both are designed to be generic apps that display sports-related data in common
formats. The goal is to allow fitness and golf mobile apps to integrate with
Pebble to show the wearer data about their activity without needing to create
and maintain an additional app for Pebble. An example of a popular app that uses
this approach is the
[Runkeeper](http://apps.getpebble.com/en_US/application/52e05bd5d8561de307000039)
app.

The Sports and Golf apps are launched, closed, and controlled by PebbleKit in an
Android or iOS app, shown by example in each section below. In both cases, the
data fields that are available to be populated are different, but data is pushed
in the same way.

## Available Data Fields

### Sports

{% screenshot_viewer %}
{
  "image": "/images/guides/design-and-interaction/sports.png",
  "platforms": [
    {"hw": "aplite", "wrapper": "steel-black"},
    {"hw": "basalt", "wrapper": "time-red"},
    {"hw": "chalk", "wrapper": "time-round-rosegold-14"}
  ]
}
{% endscreenshot_viewer %}

The sports app displays activity duration and distance which can apply to a wide
range of sports, such as cycling or running. A configurable third field is also
available that displays pace or speed, depending on the app's preference. The
Sports API also allows the app to be configured to display the labels of each
field in metric (the default) or imperial units.

The action bar is used to prompt the user to use the Select button to pause and
resume their activity session. The companion app is responsible for listening
for these events and implementing the pause/resume operation as appropriate.

### Golf

{% screenshot_viewer %}
{
  "image": "/images/guides/design-and-interaction/golf.png",
  "platforms": [
    {"hw": "aplite", "wrapper": "steel-black"},
    {"hw": "basalt", "wrapper": "time-red"},
    {"hw": "chalk", "wrapper": "time-round-rosegold-14"}
  ]
}
{% endscreenshot_viewer %}

The Golf app is specialized to displaying data relevant to golf games, including
par and hole numbers, as well as front, mid, and rear yardage.

Similar to the Sports app, the action bar is used to allow appropriate feedback
to the companion app. In this case the actions are an 'up', 'ball' and 'down'
events which the companion should handle as appropriate.

## With PebbleKit Android

Once an Android app has set up
{% guide_link communication/using-pebblekit-android %}, the Sports and Golf apps
can be launched and customized as appropriate.

### Launching Sports and Golf

To launch one of the Sports API apps, simply call `startAppOnPebble()` and
supply the UUID from the `Constants` class:

```java
// Launch the Sports app
PebbleKit.startAppOnPebble(getApplicationContext(), Constants.SPORTS_UUID);
```

### Customizing Sports

To choose which unit type is used, construct and send a `PebbleDictionary`
containing the desired value from the `Constants` class. Either
`SPORTS_UNITS_IMPERIAL` or `SPORTS_UNITS_METRIC` can be used:

```java
PebbleDictionary dict = new PebbleDictionary();

// Display imperial units
dict.addUint8(Constants.SPORTS_UNITS_KEY, Constants.SPORTS_UNITS_IMPERIAL);

PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS_UUID, dict);
```

To select between 'pace' or 'speed' as the label for the third field, construct
and send a `PebbleDictionary` similar to the example above. This can be done in
the same message as unit selection:

```java
PebbleDictionary dict = new PebbleDictionary();

// Display speed instead of pace
dict.addUint8(Constants.SPORTS_LABEL_KEY, Constants.SPORTS_DATA_SPEED);

PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS_UUID, dict);
```

> Note: The Golf app does not feature any customizable fields.

### Displaying Data

Data about the current activity can be sent to either of the Sports API apps
using a `PebbleDictionary`. For example, to show a value for duration and
distance in the Sports app:

```java
PebbleDictionary dict = new PebbleDictionary();

// Show a value for duration and distance
dict.addString(Constants.SPORTS_TIME_KEY, "12:52");
dict.addString(Constants.SPORTS_DISTANCE_KEY, "23.8");

PebbleKit.sendDataToPebble(getApplicationContext(), Constants.SPORTS_UUID, dict);
```

Read the [`Constants`](/docs/pebblekit-android/com/getpebble/android/kit/Constants)
documentation to learn about all the available parameters that can be used for
customization.

### Handling Button Events

When a button event is generated from one of the Sports API apps, a message is
sent to the Android companion app, which can be processed using a
`PebbleDataReceiver`. For example, to listen for a change in the state of the
Sports app, search for `Constants.SPORTS_STATE_KEY` in the received
`PebbleDictionary`. The user is notified in the example below through the use of
an Android
[`Toast`](http://developer.android.com/guide/topics/ui/notifiers/toasts.html):

```java
// Create a receiver for when the Sports app state changes
PebbleDataReceiver reciever = new PebbleKit.PebbleDataReceiver(
                                                        Constants.SPORTS_UUID) {

  @Override
  public void receiveData(Context context, int id, PebbleDictionary data) {
    // Always ACKnowledge the last message to prevent timeouts
    PebbleKit.sendAckToPebble(getApplicationContext(), id);

    // Get action and display as Toast
    Long value = data.getUnsignedIntegerAsLong(Constants.SPORTS_STATE_KEY);
    if(value != null) {
      int state = value.intValue();
      String text = (state == Constants.SPORTS_STATE_PAUSED)
                                                      ? "Resumed!" : "Paused!";
      Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
    }
  }

};

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

## With PebbleKit iOS

Once an iOS app has set up {% guide_link communication/using-pebblekit-ios %},
the Sports and Golf apps can be launched and customized as appropriate. The
companion app should set itself as a delegate of `PBPebbleCentralDelegate`, and
assign a `PBWatch` property once `watchDidConnect:` has fired. This `PBWatch`
object will then be used to manipulate the Sports API apps.

Read *Becoming a Delegate* in the
{% guide_link communication/using-pebblekit-ios %} guide to see how this is
done.

### Launching Sports and Golf

To launch one of the Sports API apps, simply call `sportsAppLaunch:` or
`golfAppLaunch:` as appropriate:

```objective-c
[self.watch sportsAppLaunch:^(PBWatch * _Nonnull watch,
                                                  NSError * _Nullable error) {
  NSLog(@"Sports app was launched");
}];
```

### Customizing Sports

To choose which unit type is used, call `sportsAppSetMetric:` with the desired
`isMetric` `BOOL`:

```objective-c
BOOL isMetric = YES;

[self.watch sportsAppSetMetric:isMetric onSent:^(PBWatch * _Nonnull watch,
                                                 NSError * _Nonnull error) {
  if (!error) {
    NSLog(@"Successfully sent message.");
  } else {
    NSLog(@"Error sending message: %@", error);
  }
}];
```

To select between 'pace' or 'speed' as the label for the third field, call
`sportsAppSetLabel:` with the desired `isPace` `BOOL`:

```objective-c
BOOL isPace = YES;

[self.watch sportsAppSetLabel:isPace onSent:^(PBWatch * _Nonnull watch,
                                              NSError * _Nullable error) {
  if (!error) {
    NSLog(@"Successfully sent message.");
  } else {
    NSLog(@"Error sending message: %@", error);
  }
}];
```

> Note: The Golf app does not feature any customizable fields.

### Displaying Data

Data about the current activity can be sent to either the Sports or Golf app
using `sportsAppUpdate:` or `golfAppUpdate:`. For example, to show a value for
duration and distance in the Sports app:

```objective-c
// Construct a dictionary of data
NSDictionary *update = @{ PBSportsTimeKey: @"12:34",
                          PBSportsDistanceKey: @"6.23" };

// Send the data to the Sports app
[self.watch sportsAppUpdate:update onSent:^(PBWatch * _Nonnull watch,
                                                  NSError * _Nullable error) {
  if (!error) {
    NSLog(@"Successfully sent message.");
  } else {
    NSLog(@"Error sending message: %@", error);
  }
}];
```

Read the [`PBWatch`](/docs/pebblekit-ios/Classes/PBWatch/) documentation to learn about all
the available methods and values for customization.

### Handling Button Events

When a button event is generated from one of the Sports API apps, a message is
sent to the Android companion app, which can be processed using
`sportsAppAddReceiveUpdateHandler` and supplying a block to be run when a
message is received. For example, to listen for change in state of the Sports
app, check the value of the provided `SportsAppActivityState`:

```objective-c
// Register to get state updates from the Sports app
[self.watch sportsAppAddReceiveUpdateHandler:^BOOL(PBWatch *watch,
                                                SportsAppActivityState state) {
  // Display the new state of the watchapp
  switch (state) {
    case SportsAppActivityStateRunning:
      NSLog(@"Watchapp now running.");
      break;
    case SportsAppActivityStatePaused:
      NSLog(@"Watchapp now paused.");
      break;
    default: break;
  }

  // Finally
  return YES;
}];
```


---
# 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: Common Runtime Errors
description: |
  Examples of commonly encountered runtime problems that cannot be detected at
  compile time and can usually be fixed by logical thought and experimentation.
guide_group: debugging
order: 3
---

Whether just beginning to create apps for the Pebble platform or are creating a
more complex app, the output from app logs can be very useful in tracking down
problems with an app's code. Some examples of common problems are explored here,
including some examples to help gain familiarity with compiler output.

In contrast with syntactical errors in written code (See
{% guide_link debugging/common-syntax-errors %}),
there can also be problems that only occur when the app is actually run on a
Pebble. The reason for this is that perfectly valid C code can sometimes cause
improper behavior that is incompatible with the hardware.

These problems can manifest themselves as an app crashing and very little other
information available as to what the cause was, which means that they can take
an abnormally long time to diagnose and fix.

One option to help track down the offending lines is to begin at the start of
app initialization and use a call to ``APP_LOG()`` to establish where execution
stops. If the message in the log call appears, then execution has at least
reached that point. Move the call further through logical execution until it
ceases to appear, as it will then be after the point where the app crashes.

## Null Pointers

The Pebble SDK uses a dynamic memory allocation model, meaning that all the SDK
objects and structures available for use by developers are allocated as and when
needed. This model has the advantage that only the immediately needed data and
objects can be kept in memory and unloaded when they are not needed, increasing
the scale and capability of apps that can be created.

In this paradigm a structure is first declared as a pointer (which may be given
an initial value of `NULL`) before being fully allocated a structure later in
the app's initialization. Therefore one of the most common problems that can
arise is that of the developer attempting to use an unallocated structure or
data item.

For example, the following code segment will cause a crash:

```c
Window *main_window;

static void init() {
  // Attempting to push an uninitialized Window!
  window_stack_push(main_window, true);
}
```

The compiler will not report this, but when run the app will crash before the
``Window`` can be displayed, with an error message sent to the console output
along the following lines:

```nc|text
[INFO    ] E ault_handling.c:77 App fault! {f23aecb8-bdb5-4d6b-b270-602a1940575e} PC: 0x8016716 LR: 0x8016713
[WARNING ]    Program Counter (PC):  0x8016716 ???
[WARNING ]      Link Register (LR):  0x8016713 ???
```

When possible, the pebble tool will tell the developer the PC (Program Counter,
or which statement is currently being executed) and LR (Link Register, address
to return to when the current function scope ends) addresses and line numbers at
the time of the crash, which may help indicate the source of the problem.

This problem can be fixed by ensuring that any data structures declared as
pointers are properly allocated using the appropriate `_create()` SDK functions
before they are used as arguments:

```c
Window *main_window;

static void init(void) {
  main_window = window_create();
  window_stack_push(main_window, true);
}
```

In situations where available heap space is limited, `_create()` functions may
return `NULL`, and the object will not be allocated. Apps can detect this
situation as follows:

```c
Window *main_window;

static void init(void) {
  main_window = window_create();

  if(main_window != NULL) {
    // Allocation was successful!
    window_stack_push(main_window, true);
  } else {
    // The Window could not be allocated!
    // Tell the user that the operation could not be completed
    text_layer_set_text(s_output_layer,
                                  "Unable to use this feature at the moment.");
  }
}
```

This `NULL` pointer error can also occur to any dynamically allocated structure
or variable of the developer's own creation outside the SDK. For example, a
typical dynamically allocated array will cause a crash if it is used before it
is allocated:

```c
char *array;

// Array is still NULL!
array[0] = 'a';
```

This problem can be fixed in a similar manner as before by making sure the array
is properly allocated before it is used:

```c
char *array = (char*)malloc(8 * sizeof(char));
array[0] = 'a';
```

As mentioned above for ``window_create()``, be sure also check the
[return value](http://pubs.opengroup.org/onlinepubs/009695399/functions/malloc.html)
of `malloc()` to determine whether the memory allocation requested was completed
successfully:

```c
array = (char*)malloc(8 * sizeof(char));

// Check the malloc() was successful
if(array != NULL) {
  array[0] = 'a';
} else {
  // Gracefully handle the failed situation

}
```

## Outside Array Bounds

Another problem that can look OK to the compiler, but cause an error at runtime
is improper use of arrays, such as attempting to access an array index outside
the array's bounds. This can occur when a loop is set up to iterate through an
array, but the size of the array is reduced or the loop conditions change.

For example, the array iteration below will not cause a crash, but includes the
use of 'magic numbers' that can make a program brittle and prone to errors when
these numbers change:

```c
int *array;

static void init(void) {
  array = (int*)malloc(8 * sizeof(int));

  for(int i = 0; i < 8; i++) {
    array[i] = i * i;
  }
}
```

If the size of the allocated array is reduced, the app will crash when the
iterative loop goes outside the array bounds:

```c
int *array;

static void init(void) {
  array = (int*)malloc(4 * sizeof(int));

  for(int i = 0; i < 8; i++) {
    array[i] = i * i;

    // Crash when i == 4!
  }
}
```

Since the number of loop iterations is linked to the size of the array, this
problem can be avoided by defining the size of the array in advance in one
place, and then using that value everywhere the size of the array is needed:

```c
#define ARRAY_SIZE 4

int *array;

static void init(void) {
  array = (int*)malloc(ARRAY_SIZE * sizeof(int));

  for(int i = 0; i < ARRAY_SIZE; i++) {
    array[i] = i * i;
  }
}
```

An alternative solution to the above is to use either the `ARRAY_LENGTH()` macro
or the `sizeof()` function to programmatically determine the size of the array
to be looped over.


---
# 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: Common Syntax Errors
description: |
  Details of common problems encountered when writing C apps for Pebble, and how
  to resolve them.
guide_group: debugging
order: 2
---

If a developer is relatively new to writing Pebble apps (or new to the C
language in general), there may be times when problems with an app's code will
cause compilation errors. Some types of errors with the code itself can be
detected by the compiler and this helps reduce the number that cause problems
when the code is run on Pebble.

These are problems with how app code is written, as opposed to runtime errors
(discussed in {% guide_link debugging/common-runtime-errors %}), which may
include breaking the rules of the C language or bad practices that the compiler
is able to detect and show as an error. The following are some examples.

### Undeclared Variables

This error means that a variable that has been referenced is not available in
the current scope.

```nc|text
../src/main.c: In function 'toggle_logging':
../src/main.c:33:6: error: 'is_now_logging' undeclared (first use in this function)
   if(is_now_logging == true) {
      ^
```

In the above example, the symbol `is_now_logging` has been used in the
`toggle_logging` function, but it was not first declared there. This could be
because the declaring line has been deleted, or it was expected to be available
globally, but isn't.

To fix this, consider where else the symbol is required. If it is needed in
other functions, move the declaration to a global scope (outside any function).
If it is needed only for this function, declare it before the offending line
(here line `33`).

### Undeclared Functions

Another variant of the above problem can occur when declaring new functions in a
code file. Due to the nature of C compilation, any function a
developer attempts to call must have been previously encountered by the compiler
in order to be visible. This can be done through
[forward declaration](http://en.wikipedia.org/wiki/Forward_declaration).

For example, the code segment below will not compile:

```c
static void window_load(Window *window) {
  my_function();
}

void my_function() {
  // Some code here

}
```

The compiler will report this with an 'implicit declaration' error, as the app
has implied the function's existence by calling it, even though the compiler has
not seen it previously:

```nc|text
../src/function-visibility.c: In function 'window_load':
../src/function-visibility.c:6:3: error: implicit declaration of function 'my_function' [-Werror=implicit-function-declaration]
   my_function();
   ^
```

This is because the *declaration* of `my_function()` occurs after it is called
in `window_load()`. There are two options to fix this.

* Move the function declaration above any calls to it, so it has been
  encountered by the compiler:

```c
void my_function() {
  // Some code here

}

static void window_load(Window *window) {
  my_function();
}
```

* Declare the function by prototype before it is called, and provide the
  implementation later:

```c
void my_function();

static void window_load(Window *window) {
  my_function();
}

void my_function() {
  // Some code here

}
```

### Too Few Arguments

When creating functions with argument lists, sometimes the requirements of the
function change and the developer forgets to update the places where it is
called.

```nc|text
../src/main.c: In function 'select_click_handler':
../src/main.c:57:3: error: too few arguments to function 'toggle_logging'
   toggle_logging();
   ^
../src/main.c:32:13: note: declared here
 static void toggle_logging(bool will_log) {
             ^
```

The example above reports that the app tried to call the `toggle_logging()`
function in `select_click_handler()` on line 57, but did not supply enough
arguments. The argument list expected in the function definition is shown in the
second part of the output message, which here exists on line 32 and expects an
extra value of type `bool`.

To fix this, establish which version of the function is required, and update
either the calls or the declaration to match.

### Incorrect Callback Implementations

In the Pebble SDK there are many instances where the developer must implement a
function signature required for callbacks, such as for a ``WindowHandlers``
object. This means that when implementing the handler the developer-defined
callback must match the return type and argument list specified in the API
documentation.

For example, the ``WindowHandler`` callback (used for the `load` and `unload`
events in a ``Window``'s lifecycle) has the following signature:

```c
typedef void(* WindowHandler)(struct Window *window)
```

This specifies a return type of `void` and a single argument: a pointer of type
``Window``. Therefore the implemented callback should look like this:

```c
void window_load(Window *window) {

}
```

If the developer does not specify the correct return type and argument list in
their callback implementation, the compiler will let them know with an error
like the following, stating that the type of function passed by the developer
does not match that which is expected:

```nc|text
../src/main.c: In function 'init':
../src/main.c:82:5: error: initialization from incompatible pointer type [-Werror]
     .load = main_window_load,
     ^
../src/main.c:82:5: error: (near initialization for '(anonymous).load') [-Werror]
```

To fix this, double check that the implementation provided has the same return
type and argument list as specified in the API documentation.


---
# 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: Debugging with App Logs
description: |
  How to use the app logs to debug problems with an app, as well as tips on
  interpreting common run time errors.
guide_group: debugging
order: 1
related_docs:
  - Logging
platform_choice: true
---

When apps in development do not behave as expected the developer can use app
logs to find out what is going wrong. The C SDK and PebbleKit JS can both output
messages and values to the console to allow developers to get realtime
information on the state of their app.

This guide describes how to log information from both the C and JS parts of a
watchapp or watchface and also how to read that information for debugging
purposes.

## Logging in C

The C SDK includes the ``APP_LOG()`` macro function which allows an app to
log a string containing information to the console:

```c
static int s_buffer[5];
for(int i = 0; i < 10; i++) {
  // Store loop value in array
  s_buffer[i] = i;

  APP_LOG(APP_LOG_LEVEL_DEBUG, "Loop index now %d", i);
}
```

This will result in the following output before crashing:

```nc|text
[INFO    ] D main.c:20 Loop index now 0
[INFO    ] D main.c:20 Loop index now 1
[INFO    ] D main.c:20 Loop index now 2
[INFO    ] D main.c:20 Loop index now 3
[INFO    ] D main.c:20 Loop index now 4
```

In this way it will be possible to tell the state of the loop index value if the
app encounters a problem and crashes (such as going out of array bounds in the
above example).

## Logging in JS

Information can be logged in PebbleKit JS and Pebble.js using the standard
JavaScript console, which will then be passed on to the log output view. An
example of this is to use the optional callbacks when using
`Pebble.sendAppMessage()` to know if a message was sent successfully to the
watch:

```js
console.log('Sending data to Pebble...');

Pebble.sendAppMessage({'KEY': value}, function(e) {
    console.log('Send successful!');
  }, function(e) {
    console.log('Send FAILED!');
  }
);
```

## Viewing Log Data

When viewing app logs, both the C and JS files' output are shown in the same
view.

^CP^ To view app logs in CloudPebble, open a project and navigate to the
'Compilation' screen. Click 'View App Logs' and run an app that includes log
output calls to see the output appear in this view.

^LC^ The `pebble` {% guide_link tools-and-resources/pebble-tool %} will
output any logs from C and JS files after executing the `pebble logs` command
and supplying the phone's IP address:

<div class="platform-specific" data-sdk-platform="local">
{% markdown %}
```text
pebble logs --phone=192.168.1.25
```

> Note: You can also use `pebble install --logs' to combine both of these
> operations into one command.
{% endmarkdown %}
</div>

## Memory Usage Information

In addition to the log output from developer apps, statistics about memory
usage are also included in the C app logs when an app exits:

```nc|text
[INFO] process_manager.c:289: Heap Usage for App compass-ex: Total Size <22980B> Used <164B> Still allocated <0B>
```

This piece of information reports the total heap size of the app, the amount of
memory allocated as a result of execution, and the amount of memory still
allocated when it exited. This last number can alert any forgotten deallocations
(for example, forgetting ``window_destroy()`` after ``window_create()``). A
small number such as `28B` is acceptable, provided it remains the same after
subsequent executions. If it increases after each app exit it may indicate a
memory leak.

For more information on system memory usage, checkout the
[Size presentation from the 2014 Developer Retreat](https://www.youtube.com/watch?v=8tOhdUXcSkw).

## Avoid Excessive Logging

As noted in the [API documentation](``Logging``), logging over
Bluetooth can be a power-hungry operation if an end user has the Developer
Connection enabled and is currently viewing app logs.

In addition, frequent (multiple times per second) logging can interfere with
frequent use of ``AppMessage``, as the two mechanisms share the same channel for
communication. If an app is logging sent/received AppMessage events or values
while doing this sending, it could experience slow or dropped messages. Be sure
to disable this logging when frequently sending messages.


---
# 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: Debugging with GDB
description: |
  How to use GDB to debug a Pebble app in the emulator.
guide_group: debugging
order: 0
---

As of SDK 3.10 (and [Pebble Tool](/guides/tools-and-resources/pebble-tool) 4.2),
developers can use the powerful [GDB](https://www.gnu.org/software/gdb/)
debugging tool to find and fix errors in Pebble apps while they are running in
an emulator. GDB allows the user to observe the state of the app at any point in
time, including the value of global and local variables, as well as current
function parameters and a backtrace. Strategically placing breakpoints and
observing these values can quickly reveal the source of a bug.

{% alert notice %}
GDB cannot be used to debug an app running on a real watch.
{% endalert %}

## Starting GDB

To begin using GDB, start an emulator and install an app:

```text
$ pebble install --emulator basalt
```

Once the app is installed, begin using GDB:

```text
$ pebble gdb --emulator basalt
```

Once the `(gdb)` prompt appears, the app is paused by GDB for observation. To
resume execution, use the `continue` (or `c`) command. Similarly, the app can be
paused for debugging by pressing `control + c`.

```text
(gdb) c
Continuing.
```

A short list of useful commands (many more are available) can be also be
obtained from the `pebble` tool. Read the
[*Emulator Interaction*](/guides/tools-and-resources/pebble-tool/#gdb)
section of the {% guide_link tools-and-resources/pebble-tool %} guide for more
details on this list.

```text
$ pebble gdb --help
```

## Observing App State

To see the value of variables and parameters at any point, set a breakpoint
by using the `break` (or `b`) command and specifying either a function name, or
file name with a line number. For example, the snippet below shows a typical
``TickHandler`` implementation with line numbers in comments:

```c
/* 58 */  static void tick_handler(struct tm *tick_time, TimeUnits changed) {
/* 59 */    int hours = tick_time->tm_hour;
/* 60 */    int mins = tick_time->tm_min;
/* 61 */
/* 62 */    if(hours < 10) {
/* 63 */      /* other code */
/* 64 */    }
/* 65 */  }
```

To observe the values of `hours` and `mins`, a breakpoint is set in this file at
line 61:

```text
(gdb) b main.c:61
Breakpoint 2 at 0x200204d6: file ../src/main.c, line 61.
```

> Use `info break` to see a list of all breakpoints currently registered. Each
> can be deleted with `delete n`, where `n` is the breakpoint number.

With this breakpoint set, use the `c` command to let the app continue until it
encounters the breakpoint:

```text
$ c
Continuing.
```

When execution arrives at the breakpoint, the next line will be displayed along
with the state of the function's parameters:

```text
Breakpoint 2, tick_handler (tick_time=0x20018770, units_changed=(SECOND_UNIT | MINUTE_UNIT))
    at ../src/main.c:62
62    if(hours < 10) {
```

The value of `hours` and `mins` can be found using the `info locals` command:

```text
(gdb) info locals
hours = 13
mins = 23
```

GDB can be further used here to view the state of variables using the `p`
command, such as other parts of the `tm` object beyond those being used to
assign values to `hours` and `mins`. For example, the day of the month:

```text
(gdb) p tick_time->tm_mday
$2 = 14
```

A backtrace can be generated that describes the series of function calls that
got the app to the breakpoint using the `bt` command:

```text
(gdb) bt
#0  segment_logic (this=0x200218a0) at ../src/drawable/segment.c:18
#1  0x2002033c in digit_logic (this=0x20021858) at ../src/drawable/digit.c:141
#2  0x200204c4 in pge_logic () at ../src/main.c:29
#3  0x2002101a in draw_frame_update_proc (layer=<optimized out>, ctx=<optimized out>)
    at ../src/pge/pge.c:190
#4  0x0802627c in ?? ()
#5  0x0805ecaa in ?? ()
#6  0x0801e1a6 in ?? ()
#7  0x0801e24c in app_event_loop ()
#8  0x2002108a in main () at ../src/pge/pge.c:34
#9  0x080079de in ?? ()
#10 0x00000000 in ?? ()
```

> Lines that include '??' denote a function call in the firmware. Building the
> app with `pebble build --debug` will disable some optimizations and can
> produce more readable output from GDB. However, this can increase code size
> which may break apps that are pushing the heap space limit.

## Fixing a Crash

When an app is paused for debugging, the developer can manually advance each
statement and precisely follow the path taken through the code and observe how
the state of each variable changes over time. This is very useful for tracking
down bugs caused by unusual input to functions that do not adequately check
them. For example, a `NULL` pointer.

The app code below demonstrates a common cause of an app crash, caused by a
misunderstanding of how the ``Window`` stack works. The ``TextLayer`` is created
in the `.load` handler, but this is not called until the ``Window`` is pushed
onto the stack. The attempt to set the time to the ``TextLayer`` by calling
`update_time()` before it is displayed will cause the app to crash.

```c
#include <pebble.h>

static Window *s_window;
static TextLayer *s_time_layer;

static void window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  s_time_layer = text_layer_create(bounds);
  text_layer_set_text(s_time_layer, "00:00");
  text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
}

static void update_time() {
  time_t now = time(NULL);
  struct tm *tick_time = localtime(&now);

  static char s_buffer[8];
  strftime(s_buffer, sizeof(s_buffer), "%H:%M", tick_time);
  text_layer_set_text(s_time_layer, s_buffer);
}

static void init() {
  s_window = window_create();
  window_set_window_handlers(s_window, (WindowHandlers) {
    .load = window_load
  });

  update_time();

  window_stack_push(s_window, true);
}

static void deinit() {
  window_destroy(s_window);
}

int main() {
  init();
  app_event_loop();
  deinit();
}
```

Supposing the cause of this crash was not obvious from the order of execution,
GDB can be used to identify the cause of the crash with ease. It is known that
the app crashes on launch, so the first breakpoint is placed at the beginning of
`init()`. After continuing execution, the app will pause at this location:

```text
(gdb) b init
Breakpoint 2 at 0x2002010c: file ../src/main.c, line 26.
(gdb) c
Continuing.

Breakpoint 2, main () at ../src/main.c:41
41    init();
```

Using the `step` command (or Enter key), the developer can step through all the
statements that occur during app initialization until the crash is found (and
the `app_crashed` breakpoint is encountered. Alternatively, `bt full` can be
used after the crash occurs to inspect the local variables at the time of the
crash:

```text
(gdb) c
Continuing.

Breakpoint 1, 0x0804af6c in app_crashed ()
(gdb) bt full
#0  0x0804af6c in app_crashed ()
No symbol table info available.
#1  0x0800bfe2 in ?? ()
No symbol table info available.
#2  0x0800c078 in ?? ()
No symbol table info available.
#3  0x0804c306 in ?? ()
No symbol table info available.
#4  0x080104f0 in ?? ()
No symbol table info available.
#5  0x0804c5c0 in ?? ()
No symbol table info available.
#6  0x0805e6ea in text_layer_set_text ()
No symbol table info available.
#7  0x20020168 in update_time () at ../src/main.c:22
        now = 2076
        tick_time = <optimized out>
        s_buffer = "10:38\000\000"
#8  init () at ../src/main.c:31
No locals.
#9  main () at ../src/main.c:41
No locals.
#10 0x080079de in ?? ()
No symbol table info available.
#11 0x00000000 in ?? ()
No symbol table info available.
```

The last statement to be executed before the crash is a call to
`text_layer_set_text()`, which implies that one of its input variables was bad.
It is easy to determine which by printing local variable values with the `p`
command:

```text
Breakpoint 4, update_time () at ../src/main.c:22
22    text_layer_set_text(s_time_layer, s_buffer);
(gdb) p s_time_layer
$1 = (TextLayer *) 0x0 <__pbl_app_info>
```

In this case, GDB displays `0x0` (`NULL`) for the value of `s_time_layer`, which
shows it has not yet been allocated, and so will cause `text_layer_set_text()`
to crash. And thus, the source of the crash has been methodically identified. A
simple fix here is to swap `update_time()` and ``window_stack_push()`` around so
that `init()` now becomes:

```c
static void init() {
  // Create a Window
  s_window = window_create();
  window_set_window_handlers(s_window, (WindowHandlers) {
    .load = window_load
  });

  // Display the Window
  window_stack_push(s_window, true);

  // Set the time
  update_time();
}
```

In this new version of the code the ``Window`` will be pushed onto the stack,
calling its `.load` handler in the process, and the ``TextLayer`` will be
allocated and available for use once execution subsequently reaches
`update_time()`.


---
# 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: Debugging
description: |
  How to find and fix common compilation and runtime problems in apps.
guide_group: debugging
permalink: /guides/debugging/
menu: false
generate_toc: false
related_docs:
  - Logging
hide_comments: true
---

When writing apps, everyone makes mistakes. Sometimes a simple typo or omission
can lead to all kinds of mysterious behavior or crashes. The guides in this
section are aimed at trying to help developers identify and fix a variety of
issues that can arise when writing C code (compile-time) or running the compiled
app on Pebble (runtime).

There are also a few strategies outlined here, such as app logging and other
features of the `pebble` {% guide_link tools-and-resources/pebble-tool %} that
can indicate the source of a problem in the vast majority of cases.

## 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: Benefits of Design Guidelines
description: |
  Learn the main concepts of design guidelines, why they are needed, and how
  they can help developers.
guide_group: design-and-interaction
menu: true
permalink: /guides/design-and-interaction/benefits/
generate_toc: true
order: 0
---

## What are Design Guidelines?

Design guidelines are a set of concepts and rules used to create an app's user
interface. These define how the layout on-screen at any one time should be used
to maximize the efficiency of presenting data to the user, as well as quickly
informing them how to choose their next action. An app creator may look to other
popular apps to determine how they have helped their users understand their
app's purpose, either through the use of iconography or text highlighting. They
may then want to use that inspiration to enable users of the inspiring app to
easily use their own app. If many apps use the same visual cues, then future
users will already be trained in their use when they discover them.

## What are Interaction Patterns?

Similar to design guidelines, interaction patterns define how to implement app
interactivity to maximize its efficiency. If a user can predict how the app will
behave when they take a certain action, or be able to determine which action
fits that which they want to achieve without experimentation, then an intuitive
and rewarding experience will result.

In addition to purely physical actions such as button presses and accelerometer
gestures, the virtual navigation flow should also be considered at the design
stage. It should be intuitive for the user to move around the app screens to
access the information or execute the commands as they would expect on a first
guess. An easy way to achieve this is to use a menu with the items clearly
labelling their associated actions. An alternative is to use explicit icons to
inform the user implicitly of their purpose without needing to label them all.

## Why are They Needed?

Design guidelines and interaction patterns exist to help the developer help the
user by ensuring user interface consistency across applications on the platform.
It is often the case that the developer will have no problem operating their own
watchapp because they have been intimately familiar with how it is supposed to
work since its inception. When such an app is given to users, they may receive
large amounts of feedback from confused users who feel they do not know if an
app supports the functionality they though it did, or even how to find it. By
considering a novice user from the beginning of the UI design and
implementation, this problem can be avoided.

A Pebble watchapp experience is at its best when it can be launched, used for
its purpose in the smallest amount of time, and then closed back to the
watchface. If the user must spend a long time navigating the app's UI to get to
the information they want, or the information takes a while to arrive on every
launch, the app efficiency suffers. To avoid this problem, techniques such as
implementing a list of the most commonly used options in an app (according to
the user or the whole user base) to aid fast navigation, or caching remotely
fetched data which may still be relevant from the last update will improve the
user experience.

From an interaction pattern point of view, a complex layout filled with abstract
icons may confuse a first-time user as to what each of them represents. Apps can
mitigate this problem by using icons that have pre-established meanings across
languages, such as the 'Play/Pause' icon or the 'Power' icon, seen on many forms
of devices.

## What Are the Benefits?

The main benefits of creating and following design guidelines and common
interaction patterns are summarized as follows:

* User interface consistency, which breeds familiarity and predictability.

* Clarity towards which data is most important and hence visible and usable.

* Reduced user confusion and frustration, leading to improved perception of
  apps.

* No need to include explicit usage instructions in every app to explain how it
  must be used.

* Apps that derive design from the system apps can benefit from any learned
  behavior all Pebble users may develop in using their watches out the box.

* Clearer, more efficient and better looking apps!

## Using Existing Affordances

Developers can use concepts and interaction patterns already employed in system
apps and popular 3rd party apps to lend those affordances to your own apps. An
example of this in mobile apps is the common 'swipe down to refresh' action. By
using this action in their app, many mobile app makers can benefit from users
who have already been trained to perform this action, and can free up their
app's UI for a cleaner look, or use the space that would have been used by a
'refresh' button to add an additional feature.

In a similar vein, knowing that the Back button always exits the current
``Window`` in a Pebble app, a user does not have to worry about knowing how to
navigate out of it. Similarly, developers do not have to repeatedly implement
exiting an app, as this action is a single, commonly understood pattern - just
press the Back button! On the other hand, if a developer overrides this action a
user may be confused or frustrated when the app fails to exit as they would
expect, and this could mean a negative opinion that could have been avoided.

## What's Next?

Read {% guide_link design-and-interaction/core-experience %} to learn how design
guidelines helped shape the core Pebble system experience.