---
# 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: Event Services
description: |
  How to use the various asynchronous event services to power app features.
guide_group: events-and-services
order: 5
related_docs:
  - TickTimerService
  - ConnectionService
  - AccelerometerService
  - BatteryStateService
  - HealthService
  - AppFocusService
  - CompassService
---

All Pebble apps are executed in three phases, which are summarized below:

* Initialization - all code from the beginning of `main()` is run to set up all
  the components of the app.

* Event Loop - the app waits for and responds to any event services it has
  subscribed to.

* Deinitialization - when the app is exiting (i.e.: the user has pressed Back
  from the last ``Window`` in the stack) ``app_event_loop()`` returns, and all
  deinitialization code is run before the app exits.

Once ``app_event_loop()`` is called, execution of `main()` pauses and all
further activities are performed when events from various ``Event Service``
types occur. This continues until the app is exiting, and is typically handled
in the following pattern:

```c
static void init() {
  // Initialization code here
}

static void deinit() {
  // Deinitialization code here
}

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

## Types of Events

There are multiple types of events an app can receive from various event
services. These are described in the table below, along with their handler
signature and a brief description of what they do:

| Event Service | Handler(s) | Description |
|---------------|------------|-------------|
| ``TickTimerService`` | ``TickHandler`` | Most useful for watchfaces. Allows apps to be notified when a second, minute, hour, day, month or year ticks by. |
| ``ConnectionService`` | ``ConnectionHandler`` | Allows apps to know when the Bluetooth connection with the phone connects and disconnects. |
| ``AccelerometerService`` | ``AccelDataHandler``<br/>``AccelTapHandler`` | Allows apps to receive raw data or tap events from the onboard accelerometer. |
| ``BatteryStateService`` | ``BatteryStateHandler`` | Allows apps to read the state of the battery, as well as whether the watch is plugged in and charging. |
| ``HealthService`` | ``HealthEventHandler`` | Allows apps to be notified to changes in various ``HealthMetric`` values as the user performs physical activities. |
| ``AppFocusService`` | ``AppFocusHandler`` | Allows apps to know when they are obscured by another window, such as when a notification modal appears. |
| ``CompassService`` | ``CompassHeadingHandler`` | Allows apps to read a compass heading, including calibration status of the sensor. |

In addition, many other APIs also operate through the use of various callbacks
including ``MenuLayer``, ``AppMessage``, ``Timer``, and ``Wakeup``, but these
are not considered to be 'event services' in the same sense.

## Using Event Services

The event services described in this guide are all used in the same manner - the
app subscribes an implementation of one or more handlers, and is notified by the
system when an event of that type occurs. In addition, most also include a
'peek' style API to read a single data item or status value on demand. This can
be useful to determine the initial service state when a watchapp starts. Apps
can subscribe to as many of these services as they require, and can also
unsubscribe at any time to stop receiving events.

Each event service is briefly discussed below with multiple snippets - handler
implementation example, subscribing to the service, and any 'peek' API.

### Tick Timer Service

The ``TickTimerService`` allows an app to be notified when different units of
time change. This is decided based upon the ``TimeUnits`` value specified when a
subscription is added.

The [`struct tm`](http://www.cplusplus.com/reference/ctime/tm/) pointer provided
in the handler is a standard C object that contains many data fields describing
the current time. This can be used with
[`strftime()`](http://www.cplusplus.com/reference/ctime/strftime/) to obtain a
human-readable string.

```c
static void tick_handler(struct tm *tick_time, TimeUnits changed) {
  static char s_buffer[8];

  // Read time into a string buffer
  strftime(s_buffer, sizeof(s_buffer), "%H:%M", tick_time);

  APP_LOG(APP_LOG_LEVEL_INFO, "Time is now %s", s_buffer);
}
```

```c
// Get updates when the current minute changes
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
```

> The ``TickTimerService`` has no 'peek' API, but a similar effect can be
> achieved using the ``time()`` and ``localtime()`` APIs.

### Connection Service

The ``ConnectionService`` uses a handler for each of two connection types:

* `pebble_app_connection_handler` - the connection to the Pebble app on the
  phone, analogous with the bluetooth connection state.

* `pebblekit_connection_handler` - the connection to an iOS companion app, if
  applicable. Will never occur on Android.

Either one is optional, but at least one must be specified for a valid
subscription.

```c
static void app_connection_handler(bool connected) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Pebble app %sconnected", connected ? "" : "dis");
}

static void kit_connection_handler(bool connected) {
  APP_LOG(APP_LOG_LEVEL_INFO, "PebbleKit %sconnected", connected ? "" : "dis");
}
```

```c
connection_service_subscribe((ConnectionHandlers) {
  .pebble_app_connection_handler = app_connection_handler,
  .pebblekit_connection_handler = kit_connection_handler
});
```

```c
// Peek at either the Pebble app or PebbleKit connections
bool app_connection = connection_service_peek_pebble_app_connection();
bool kit_connection = connection_service_peek_pebblekit_connection();
```

### Accelerometer Service

The ``AccelerometerService`` can be used in two modes - tap events and raw data
events. ``AccelTapHandler`` and ``AccelDataHandler`` are used for each of these
respective use cases. See the
{% guide_link events-and-services/accelerometer %} guide for more
information.

**Data Events**

```c
static void accel_data_handler(AccelData *data, uint32_t num_samples) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Got %d new samples", (int)num_samples);
}
```

```c
const int num_samples = 10;

// Subscribe to data events
accel_data_service_subscribe(num_samples, accel_data_handler);
```

```c
// Peek at the last reading
AccelData data;
accel_service_peek(&data);
```

**Tap Events**

```c
static void accel_tap_handler(AccelAxisType axis, int32_t direction) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Tap event received");
}
```

```c
// Subscribe to tap events
accel_tap_service_subscribe(accel_tap_handler);
```

### Battery State Service

The ``BatteryStateService`` allows apps to examine the state of the battery, and
whether or not is is plugged in and charging.

```c
static void battery_state_handler(BatteryChargeState charge) {
  // Report the current charge percentage
  APP_LOG(APP_LOG_LEVEL_INFO, "Battery charge is %d%%",
                                                    (int)charge.charge_percent);
}
```

```c
// Get battery state updates
battery_state_service_subscribe(battery_state_handler);
```

```c
// Peek at the current battery state
BatteryChargeState state = battery_state_service_peek();
```

### Health Service

The ``HealthService`` uses the ``HealthEventHandler`` to notify a subscribed app
when new data pertaining to a ``HealthMetric`` is available. See the
{% guide_link events-and-services/health %} guide for more information.

```c
static void health_handler(HealthEventType event, void *context) {
  if(event == HealthEventMovementUpdate) {
    APP_LOG(APP_LOG_LEVEL_INFO, "New health movement event");
  }
}
```

```c
// Subscribe to health-related events
health_service_events_subscribe(health_handler, NULL);
```

### App Focus Service

The ``AppFocusService`` operates in two modes - basic and complete.

**Basic Subscription**

A basic subscription involves only one handler which will be fired when the app
is moved in or out of focus, and any animated transition has completed.

```c
static void focus_handler(bool in_focus) {
  APP_LOG(APP_LOG_LEVEL_INFO, "App is %s in focus", in_focus ? "now" : "not");
}
```

```c
// Add a basic subscription
app_focus_service_subscribe(focus_handler);
```

**Complete Subscription**

A complete subscription will notify the app with more detail about changes in
focus using two handlers in an ``AppFocusHandlers`` object:

* `.will_focus` - represents a change in focus that is *about* to occur, such as
  the start of a transition animation to or from a modal window. `will_focus`
  will be `true` if the app will be in focus at the end of the transition.

* `.did_focus` - represents the end of a transition. `did_focus` will be `true`
  if the app is now completely in focus and the animation has finished.

```c
void will_focus_handler(bool will_focus) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Will %s focus", will_focus ? "gain" : "lose");
}

void did_focus_handler(bool did_focus) {
  APP_LOG(APP_LOG_LEVEL_INFO, "%s focus", did_focus ? "Gained" : "Lost");
}
```

```c
// Subscribe to both types of events
app_focus_service_subscribe_handlers((AppFocusHandlers) {
  .will_focus = will_focus_handler,
  .did_focus = did_focus_handler
});
```

### Compass Service

The ``CompassService`` provides access to regular updates about the watch's
magnetic compass heading, if it is calibrated. See the
{% guide_link events-and-services/compass %} guide for more information.

```c
static void compass_heading_handler(CompassHeadingData heading_data) {
  // Is the compass calibrated?
  if(heading_data.compass_status == CompassStatusCalibrated) {
    APP_LOG(APP_LOG_LEVEL_INFO, "Calibrated! Heading is %ld",
                             TRIGANGLE_TO_DEG(heading_data.magnetic_heading));
  }
}
```

```c
// Subscribe to compass heading updates
compass_service_subscribe(compass_heading_handler);
```

```c
// Peek the compass heading data
CompassHeadingData data;
compass_service_peek(&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: Pebble Health
description: |
  Information on using the HealthService API to incorporate multiple types of
  health data into your apps.
guide_group: events-and-services
order: 6
platforms:
  - basalt
  - chalk
  - diorite
  - emery
related_docs:
  - HealthService
related_examples:
  - title: Simple Health Example
    url: https://github.com/pebble-examples/simple-health-example
---

[Pebble Health](https://blog.getpebble.com/2015/12/15/health/) provides builtin
health data tracking to allow users to improve their activity and sleep habits.
With SDK 3.9, the ``HealthService`` API opens this data up to developers to
include and use within their apps. For example, a watchface could display a
brief summary of the user's activity for the day.

## API Availability

In order to use the ``HealthService`` (and indeed Pebble Health), the user must
enable the 'Pebble Health' app in the 'Apps/Timeline' view of the official
Pebble mobile app. If this is not enabled health data will not be available to
apps, and API calls will return values to reflect this.

In addition, any app using the ``HealthService`` API must declare the 'health'
capability in order to be accepted by the
[developer portal](https://dev-portal.getpebble.com/). This can be done in
CloudPebble 'Settings', or in `package.json` in the local SDK:

```js
"capabilities": [ "health" ]
```

Since Pebble Health is not available on the Aplite platform, developers should
check the API return values and hence the lack of ``HealthService`` on that
platform gracefully. In addition, the `PBL_HEALTH` define and
`PBL_IF_HEALTH_ELSE()` macro can be used to selectively omit affected code.

## Available Metrics

The ``HealthMetric`` `enum` lists the types of data (or 'metrics') that can be
read using the API. These are described below:

| Metric | Description |
|--------|-------------|
| `HealthMetricStepCount` | The user's step count. |
| `HealthMetricActiveSeconds` | Duration of time the user was considered 'active'. |
| `HealthMetricWalkedDistanceMeters` | Estimation of the distance travelled in meters. |
| `HealthMetricSleepSeconds` | Duration of time the user was considered asleep. |
| `HealthMetricSleepRestfulSeconds` | Duration of time the user was considered in deep restful sleep. |
| `HealthMetricRestingKCalories` | The number of kcal (thousand calories) burned due to resting metabolism. |
| `HealthMetricActiveKCalories` | The number of kcal (thousand calories) burned due to activity. |
| `HealthMetricHeartRateBPM` | The heart rate, in beats per minute. |

## Subscribing to HealthService Events

Like other Event Services, an app can subscribe a handler function to receive a
callback when new health data is available. This is useful for showing
near-realtime activity updates. The handler must be a suitable implementation of
``HealthEventHandler``. The `event` parameter describes the type of each update,
and is one of the following from the ``HealthEventType`` `enum`:

| Event Type | Value | Description |
|------------|-------|-------------|
| `HealthEventSignificantUpdate` | `0` | All data is considered as outdated, apps should re-read all health data. This can happen on a change of the day or in other cases that significantly change the underlying data. |
| `HealthEventMovementUpdate` | `1` | Recent values around `HealthMetricStepCount`, `HealthMetricActiveSeconds`, `HealthMetricWalkedDistanceMeters`, and `HealthActivityMask` changed. |
| `HealthEventSleepUpdate` | `2` | Recent values around `HealthMetricSleepSeconds`, `HealthMetricSleepRestfulSeconds`, `HealthActivitySleep`, and `HealthActivityRestfulSleep` changed. |
| `HealthEventHeartRateUpdate` | `4` | The value of `HealthMetricHeartRateBPM` has changed. |

A simple example handler is shown below, which outputs to app logs the type of
event that fired the callback:

```c
static void health_handler(HealthEventType event, void *context) {
  // Which type of event occurred?
  switch(event) {
    case HealthEventSignificantUpdate:
      APP_LOG(APP_LOG_LEVEL_INFO,
              "New HealthService HealthEventSignificantUpdate event");
      break;
    case HealthEventMovementUpdate:
      APP_LOG(APP_LOG_LEVEL_INFO,
              "New HealthService HealthEventMovementUpdate event");
      break;
    case HealthEventSleepUpdate:
      APP_LOG(APP_LOG_LEVEL_INFO,
              "New HealthService HealthEventSleepUpdate event");
      break;
    case HealthEventHeartRateUpdate:
      APP_LOG(APP_LOG_LEVEL_INFO,
              "New HealthService HealthEventHeartRateUpdate event");
      break;
  }
}
```

The subscription is then registered in the usual way, optionally providing a
`context` parameter that is relayed to each event callback. The return value
should be used to determine whether the subscription was successful:

```c
#if defined(PBL_HEALTH)
// Attempt to subscribe
if(!health_service_events_subscribe(health_handler, NULL)) {
  APP_LOG(APP_LOG_LEVEL_ERROR, "Health not available!");
}
#else
APP_LOG(APP_LOG_LEVEL_ERROR, "Health not available!");
#endif
```

## Reading Health Data

Health data is collected in the background as part of Pebble Health regardless
of the state of the app using the ``HealthService`` API, and is available to
apps through various ``HealthService`` API functions.

Before reading any health data, it is recommended to check that data is
available for the desired time range, if applicable. In addition to the
``HealthServiceAccessibilityMask`` value, health-related code can be
conditionally compiled using `PBL_HEALTH`. For example, to check whether
any data is available for a given time range:

```c
#if defined(PBL_HEALTH)
// Use the step count metric
HealthMetric metric = HealthMetricStepCount;

// Create timestamps for midnight (the start time) and now (the end time)
time_t start = time_start_of_today();
time_t end = time(NULL);

// Check step data is available
HealthServiceAccessibilityMask mask = health_service_metric_accessible(metric,
                                                                    start, end);
bool any_data_available = mask & HealthServiceAccessibilityMaskAvailable;
#else
// Health data is not available here
bool any_data_available = false;
#endif
```

Most applications will want to read the sum of a metric for the current day's
activity. This is the simplest method for accessing summaries of users' health
data, and is shown in the example below:

```c
HealthMetric metric = HealthMetricStepCount;
time_t start = time_start_of_today();
time_t end = time(NULL);

// Check the metric has data available for today
HealthServiceAccessibilityMask mask = health_service_metric_accessible(metric,
  start, end);

if(mask & HealthServiceAccessibilityMaskAvailable) {
  // Data is available!
  APP_LOG(APP_LOG_LEVEL_INFO, "Steps today: %d",
          (int)health_service_sum_today(metric));
} else {
  // No data recorded yet today
  APP_LOG(APP_LOG_LEVEL_ERROR, "Data unavailable!");
}
```

For more specific data queries, the API also allows developers to request data
records and sums of metrics from a specific time range. If data is available, it
can be read as a sum of all values recorded between that time range. You can use
the convenience constants from ``Time``, such as ``SECONDS_PER_HOUR`` to adjust
a timestamp relative to the current moment returned by ``time()``.

> Note: The value returned will be an average since midnight, weighted for the
> length of the specified time range. This may change in the future.

An example of this process is shown below:

```c
// Make a timestamp for now
time_t end = time(NULL);

// Make a timestamp for the last hour's worth of data
time_t start = end - SECONDS_PER_HOUR;

// Check data is available
HealthServiceAccessibilityMask result =
    health_service_metric_accessible(HealthMetricStepCount, start, end);
if(result & HealthServiceAccessibilityMaskAvailable) {
  // Data is available! Read it
  HealthValue steps = health_service_sum(HealthMetricStepCount, start, end);

  APP_LOG(APP_LOG_LEVEL_INFO, "Steps in the last hour: %d", (int)steps);
} else {
  APP_LOG(APP_LOG_LEVEL_ERROR, "No data available!");
}
```

## Representing Health Data

Depending on the locale of the user, the conventional measurement system used to
represent distances may vary between metric and imperial. For this reason it is
recommended to query the user's preferred ``MeasurementSystem`` before
formatting distance data from the ``HealthService``:

> Note: This API is currently only meaningful when querying the
> ``HealthMetricWalkedDistanceMeters`` metric. ``MeasurementSystemUnknown`` will
> be returned for all other queries.

```c
const HealthMetric metric = HealthMetricWalkedDistanceMeters;
const HealthValue distance = health_service_sum_today(metric);

// Get the preferred measurement system
MeasurementSystem system = health_service_get_measurement_system_for_display(
                                                                        metric);

// Format accordingly
static char s_buffer[32];
switch(system) {
  case MeasurementSystemMetric:
    snprintf(s_buffer, sizeof(s_buffer), "Walked %d meters", (int)distance);
    break;
  case MeasurementSystemImperial: {
    // Convert to imperial first
    int feet = (int)((float)distance * 3.28F);
    snprintf(s_buffer, sizeof(s_buffer), "Walked %d feet", (int)feet);
  } break;
  case MeasurementSystemUnknown:
  default:
    APP_LOG(APP_LOG_LEVEL_INFO, "MeasurementSystem unknown or does not apply");
}

// Display to user in correct units
text_layer_set_text(s_some_layer, s_buffer);
```

## Obtaining Averaged Data

The ``HealthService`` also allows developers to read average values of a
particular ``HealthMetric`` with varying degrees of scope. This is useful for
apps that wish to display an average value (e.g.: as a goal for the user)
alongside a summed value.

In this context, the `start` and `end` parameters specify the time period to be
used for the daily average calculation. For example, a start time of midnight
and an end time ten hours later will return the average value for the specified
metric measured until 10 AM on average across the days specified by the scope.

The ``HealthServiceTimeScope`` specified when querying for averaged data over a
given time range determines how the average is calculated, as detailed in the
table below:

| Scope Type | Description |
|------------|-------------|
| `HealthServiceTimeScopeOnce` | No average computed. The result is the same as calling ``health_service_sum()``. |
| `HealthServiceTimeScopeWeekly` | Compute average using the same day from each week (up to four weeks). For example, every Monday if the provided time range falls on a Monday. |
| `HealthServiceTimeScopeDailyWeekdayOrWeekend` | Compute average using either weekdays (Monday to Friday) or weekends (Saturday and Sunday), depending on which day the provided time range falls. |
| `HealthServiceTimeScopeDaily` | Compute average across all days of the week. |

> Note: If the difference between the start and end times is greater than one
> day, an average will be returned that takes both days into account. Similarly,
> if the time range crosses between scopes (such as including weekend days and
> weekdays with ``HealthServiceTimeScopeDailyWeekdayOrWeekend``), the start time
> will be used to determine which days are used.

Reading averaged data values works in a similar way to reading sums. The example
below shows how to read an average step count across all days of the week for a
given time range:

```c
// Define query parameters
const HealthMetric metric = HealthMetricStepCount;
const HealthServiceTimeScope scope = HealthServiceTimeScopeDaily;

// Use the average daily value from midnight to the current time
const time_t start = time_start_of_today();
const time_t end = time(NULL);

// Check that an averaged value is accessible
HealthServiceAccessibilityMask mask =
          health_service_metric_averaged_accessible(metric, start, end, scope);
if(mask & HealthServiceAccessibilityMaskAvailable) {
  // Average is available, read it
  HealthValue average = health_service_sum_averaged(metric, start, end, scope);

  APP_LOG(APP_LOG_LEVEL_INFO, "Average step count: %d steps", (int)average);
}
```

## Detecting Activities

It is possible to detect when the user is sleeping using a
``HealthActivityMask`` value. A useful application of this information could be
to disable a watchface's animations or tick at a reduced rate once the user is
asleep. This is done by checking certain bits of the returned value:

```c
// Get an activities mask
HealthActivityMask activities = health_service_peek_current_activities();

// Determine which bits are set, and hence which activity is active
if(activities & HealthActivitySleep) {
  APP_LOG(APP_LOG_LEVEL_INFO, "The user is sleeping.");
} else if(activities & HealthActivityRestfulSleep) {
  APP_LOG(APP_LOG_LEVEL_INFO, "The user is sleeping peacefully.");
} else {
  APP_LOG(APP_LOG_LEVEL_INFO, "The user is not currently sleeping.");
}
```

## Read Per-Minute History

The ``HealthMinuteData`` structure contains multiple types of activity-related
data that are recorded in a minute-by-minute fashion. This style of data access
is best suited to those applications requiring more granular detail (such as
creating a new fitness algorithm). Up to seven days worth of data is available
with this API.

> See [*Notes on Minute-level Data*](#notes-on-minute-level-data) below for more
> information on minute-level data.

The data items contained in the ``HealthMinuteData`` structure are summarized
below:

| Item | Type | Description |
|------|------|-------------|
| `steps` | `uint8_t`  | Number of steps taken in this minute. |
| `orientation` | `uint8_t` | Quantized average orientation, encoding the x-y plane (the "yaw") in the lower 4 bits (360 degrees linearly mapped to 1 of 16 values) and the z axis (the "pitch") in the upper 4 bits. |
| `vmc` | `uint16_t` | Vector Magnitude Counts (VMC). This is a measure of the total amount of movement seen by the watch. More vigorous movement yields higher VMC values. |
| `is_invalid` | `bool` | `true` if the item doesn't represent actual data, and should be ignored. |
| `heart_rate_bpm` | `uint8_t` | Heart rate in beats per minute (if available). |

These data items can be obtained in the following manner, similar to obtaining a
sum.

```c
// Create an array to store data
const uint32_t max_records = 60;
HealthMinuteData *minute_data = (HealthMinuteData*)
                              malloc(max_records * sizeof(HealthMinuteData));

// Make a timestamp for 15 minutes ago and an hour before that
time_t end = time(NULL) - (15 * SECONDS_PER_MINUTE);
time_t start = end - SECONDS_PER_HOUR;

// Obtain the minute-by-minute records
uint32_t num_records = health_service_get_minute_history(minute_data,
                                                  max_records, &start, &end);
APP_LOG(APP_LOG_LEVEL_INFO, "num_records: %d", (int)num_records);

// Print the number of steps for each minute
for(uint32_t i = 0; i < num_records; i++) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Item %d steps: %d", (int)i,
          (int)minute_data[i].steps);
}
```

Don't forget to free the array once the data is finished with:

```c
// Free the array
free(minute_data);
```

### Notes on Minute-level Data

Missing minute-level records can occur if the watch is reset, goes into low
power (watch-only) mode due to critically low battery, or Pebble Health is
disabled during the time period requested.

``health_service_get_minute_history()`` will return as many **consecutive**
minute-level records that are available after the provided `start` timestamp,
skipping any missing records until one is found. This API behavior enables one
to easily continue reading data after a previous query encountered a missing
minute. If there are some minutes with missing data, the API will return all
available records up to the last available minute, and no further. Conversely,
records returned will begin with the first available record after the provided
`start` timestamp, skipping any missing records until one is found. This can
be used to continue reading data after a previous query encountered a missing
minute.

The code snippet below shows an example function that packs a provided
``HealthMinuteData`` array with all available values in a time range, up to an
arbitrary maximum number. Any missing minutes are collapsed, so that as much
data can be returned as is possible for the allocated array size and time range
requested.

> This example shows querying up to 60 records. More can be obtained, but this
> increases the heap allocation required as well as the time taken to process
> the query.

```c
static uint32_t get_available_records(HealthMinuteData *array, time_t query_start,
                                      time_t query_end, uint32_t max_records) {
  time_t next_start = query_start;
  time_t next_end = query_end;
  uint32_t num_records_found = 0;

  // Find more records until no more are returned
  while (num_records_found < max_records) {
    int ask_num_records = max_records - num_records_found;
    uint32_t ret_val = health_service_get_minute_history(&array[num_records_found],
                                        ask_num_records, &next_start, &next_end);
    if (ret_val == 0) {
      // a 0 return value means no more data is available
      return num_records_found;
    }
    num_records_found += ret_val;
    next_start = next_end;
    next_end = query_end;
  }

  return num_records_found;
}

static void print_last_hours_steps() {
  // Query for the last hour, max 60 minute-level records
  // (except the last 15 minutes)
  const time_t query_end = time(NULL) - (15 * SECONDS_PER_MINUTE);
  const time_t query_start = query_end - SECONDS_PER_HOUR;
  const uint32_t max_records = (query_end - query_start) / SECONDS_PER_MINUTE;
  HealthMinuteData *data =
              (HealthMinuteData*)malloc(max_records * sizeof(HealthMinuteData));

  // Populate the array
  max_records = get_available_records(data, query_start, query_end, max_records);

  // Print the results
  for(uint32_t i = 0; i < max_records; i++) {
    if(!data[i].is_invalid) {
      APP_LOG(APP_LOG_LEVEL_INFO, "Record %d contains %d steps.", (int)i,
                                                            (int)data[i].steps);
    } else {
      APP_LOG(APP_LOG_LEVEL_INFO, "Record %d was not valid.", (int)i);
    }
  }

  free(data);
}
```

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: Heart Rate Monitor
description: |
  Information on using the HealthService API to obtain information from the
  Heart Rate Monitor.
guide_group: events-and-services
order: 6
related_docs:
  - HealthService
related_examples:
  - title: HRM Watchface
    url: https://github.com/pebble-examples/watchface-tutorial-hrm
  - title: HRM Activity
    url: https://github.com/pebble-examples/hrm-activity-example
platform_choice: true
---

The Pebble Time 2 and Pebble 2 (excluding SE model)
{% guide_link tools-and-resources/hardware-information "devices" %} include a
heart rate monitor. This guide will demonstrate how to use the ``HealthService``
API to retrieve information about the user's current, and historical heart
rates.

If you aren't already familiar with the ``HealthService``, we recommended that
you read the {% guide_link events-and-services/health "Health guide" %}
before proceeding.

## Enable Health Data

<div class="platform-specific" data-sdk-platform="local">
{% markdown {} %}
Before your application is able to access the heart rate information, you will
need to add `heath` to the `capabilities` array in your applications
`package.json` file.

```js
{
  ...
  "pebble": {
    ...
    "capabilities": [ "health" ],
    ...
  }
}
```
{% endmarkdown %}
</div>

^CP^ Before your application is able to access heart rate information, you will
need to enable the `USES HEALTH` option in your project settings on
[CloudPebble]({{ site.data.links.cloudpebble }}).

## Data Quality

Heart rate sensors aren't perfect, and their readings can be affected by
improper positioning, environmental factors and excessive movement. The raw data
from the HRM sensor contains a metric to indicate the quality of the readings it
receives.

The HRM API provides a raw BPM reading (``HealthMetricHeartRateRawBPM``) and a
filtered reading (``HealthMetricHeartRateBPM``). This filtered value minimizes
the effect of hand movement and improper sensor placement, by removing the bad
quality readings. This filtered data makes it easy for developers to integrate
in their applications, without needing to filter the data themselves.

## Obtaining the Current Heart Rate

To obtain the current heart rate, you should first check whether the
``HealthMetricHeartRateBPM`` is available by using the
``health_service_metric_accessible`` method.

Then you can obtain the current heart rate using the
``health_service_peek_current_value`` method:

```c
HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateBPM, time(NULL), time(NULL));
if (hr & HealthServiceAccessibilityMaskAvailable) {
  HealthValue val = health_service_peek_current_value(HealthMetricHeartRateBPM);
  if(val > 0) {
    // Display HRM value
    static char s_hrm_buffer[8];
    snprintf(s_hrm_buffer, sizeof(s_hrm_buffer), "%lu BPM", (uint32_t)val);
    text_layer_set_text(s_hrm_layer, s_hrm_buffer);
  }
}
```
> **Note** this value is averaged from readings taken over the past minute, but
due to the [sampling rate](#heart-rate-sample-periods) and our data filters,
this value could be several minutes old. Use `HealthMetricHeartRateRawBPM` for
the raw, unfiltered value.

## Subscribing to Heart Rate Updates

The user's heart rate can also be monitored via an event subscription, in a
similar way to the other health metrics. If you wanted your watchface to update
the displayed heart rate every time the HRM takes a new reading, you could use
the ``health_service_events_subscribe`` method.

```c

static void prv_on_health_data(HealthEventType type, void *context) {
  // If the update was from the Heart Rate Monitor, query it
  if (type == HealthEventHeartRateUpdate) {
    HealthValue value = health_service_peek_current_value(HealthMetricHeartRateBPM);
    // Display the heart rate
  }
}

static void prv_init(void) {
  // Subscribe to health event handler
  health_service_events_subscribe(prv_on_health_data, NULL);
  // ...
}
```

> **Note** The frequency of these updates does not directly correlate to the
> sensor sampling rate.

## Heart Rate Sample Periods

The default sample period is 10 minutes, but the system automatically controls
the HRM sample rate based on the level of user activity. It increases the
sampling rate during intense activity and reduces it again during inactivity.
This aims to provide the optimal battery usage.

### Battery Considerations

Like all active sensors, accelerometer, backlight etc, the HRM sensor will have
a negative affect on battery life. It's important to consider this when using
the APIs within your application.

By default the system will automatically control the heart rate sampling period
for the optimal balance between update frequency and battery usage. In addition,
the APIs have been designed to allow developers to retrieve values for the most
common use cases with minimal impact on battery life.

### Altering the Sampling Period

Developers can request a specific sampling rate using the
``health_service_set_heart_rate_sample_period`` method. The system will use this
value as a suggestion, but does not guarantee that value will be used. The
actual sampling period may be greater or less due to other apps that require
input from the sensor, or data quality issues.

The shortest period you can currently specify is `1` second, and the longest
period you can specify is `600` seconds (10 minutes).

In this example, we will sample the heart rate monitor every second:

```c
// Set the heart rate monitor to sample every second
bool success = health_service_set_heart_rate_sample_period(1);
```
> **Note** This does not mean that you can peek the current value every second,
> only that the sensor will capture more samples.

### Resetting the Sampling Period

Developers **must** reset the heart rate sampling period when their application
exits. Failing to do so may result in the heart rate monitor continuing at the
increased rate for a period of time, even after your application closes. This
is fundamentally different to other Pebble sensors and was designed so that
applications which a reliant upon high sampling rates can be temporarily
interupted for notifications, or music, without affecting the sensor data.

```c
// Reset the heart rate sampling period to automatic
health_service_set_heart_rate_sample_period(0);
```

## Obtaining Historical Data

If your application is using heart rate information, it may also want to obtain
historical data to compare it against. In this section we'll look at how you can
use the `health_service_aggregate` functions to obtain relevant historic data.

Before requesting historic/aggregate data for a specific time period, you
should ensure that it is available using the
``health_service_metric_accessible`` method.

Then we'll use the ``health_service_aggregate_averaged`` method to
obtain the average daily heart rate over the last 7 days.

```c
// Obtain history for last 7 days
time_t end_time = time(NULL);
time_t start_time = end_time - (7 * SECONDS_PER_DAY);

HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateBPM, start_time, end_time);
if (hr & HealthServiceAccessibilityMaskAvailable) {
  uint32_t weekly_avg_hr = health_service_aggregate_averaged(HealthMetricHeartRateBPM,
                              start_time, end_time,
                              HealthAggregationAvg, HealthServiceTimeScopeDaily);
}
```

You can also query the average `min` and `max` heart rates, but only within the
past two hours (maximum). This limitation is due to very limited storage
capacity on the device, but the implementation may change in the future.

```c
// Obtain history for last 1 hour
time_t end_time = time(NULL);
time_t start_time = end_time - SECONDS_PER_HOUR;

HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateBPM, start_time, end_time);
if (hr & HealthServiceAccessibilityMaskAvailable) {
  uint32_t min_hr = health_service_aggregate_averaged(HealthMetricHeartRateBPM,
                                start_time, end_time,
                                HealthAggregationMin, HealthServiceTimeScopeOnce);
  uint32_t max_hr = health_service_aggregate_averaged(HealthMetricHeartRateBPM,
                                start_time, end_time,
                                HealthAggregationMax, HealthServiceTimeScopeOnce);
}
```

## Read Per-Minute History

The ``HealthMinuteData`` structure contains multiple types of activity-related
data that are recorded in a minute-by-minute fashion. Although this structure
now contains HRM readings, it does not contain information about the quality of
those readings.

> **Note** Please refer to the
> {% guide_link events-and-services/health#read-per-minute-history "Health Guide" %}
> for futher information.

## Next Steps

This guide covered the basics of how to interact with realtime and historic
heart information. We encourage you to further explore the ``HealthService``
documentation, and integrate it into your next project.


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

title: Events and Services
description: |
  How to get data from the onboard sensors including the accelerometer, compass,
  and microphone.
guide_group: events-and-services
menu: false
permalink: /guides/events-and-services/
generate_toc: false
hide_comments: true
---

All Pebble watches contain a collection of sensors than can be used as input
devices for apps. Available sensors include four buttons, an accelerometer, and
a magnetometer (accessible via the ``CompassService`` API). In addition, the
Basalt and Chalk platforms also include a microphone (accessible via the
``Dictation`` API) and access to Pebble Health data sets. Read
{% guide_link tools-and-resources/hardware-information %} for more information
on sensor availability per platform.

While providing more interactivity, excessive regular use of these sensors will
stop the watch's CPU from sleeping and result in faster battery drain, so use
them sparingly. An alternative to constantly reading accelerometer data is to
obtain data in batches, allowing sleeping periods in between. Read
{% guide_link best-practices/conserving-battery-life %} for more information.

## 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: Persistent Storage
description: |
  Using persistent storage to improve your app's UX.
guide_group: events-and-services
order: 7
related_docs:
  - Storage
---

Developers can use the ``Storage`` API to persist multiple types of data between
app launches, enabling apps to remember information previously entered by the
user. A common use-case of this API is to enable the app to remember
configuration data chosen in an app's configuration page, removing the
tedious need to enter the information on the phone every time the watchapp is
launched. Other use cases include to-to lists, stat trackers, game highscores
etc.

Read {% guide_link user-interfaces/app-configuration %} for more information on
implementing an app configuration page.

## Persistent Storage Model

Every app is allocated 4 kB of persistent storage space and can write values to
storage using a key, similar to ``AppMessage`` dictionaries or the web
`localStorage` API. To recall values, the app simply queries the API using the
associated key . Keys are specified in the `uint32_t` type, and each value can
have a size up to ``PERSIST_DATA_MAX_LENGTH`` (currently 256 bytes).

When an app is updated the values saved using the ``Storage`` API will be
persisted, but if it is uninstalled they will be removed.

Apps that make large use of the ``Storage`` API may experience small pauses due
to underlying housekeeping operations. Therefore it is recommended to read and
write values when an app is launching or exiting, or during a time the user is
waiting for some other action to complete.

## Types of Data

Values can be stored as boolean, integer, string, or arbitrary data structure
types. Before retrieving a value, the app should check that it has been
previously persisted. If it has not, a default value should be chosen as
appropriate.

```c
uint32_t key = 0;
int num_items = 0;

if (persist_exists(key)) {
  // Read persisted value
  num_items = persist_read_int(key);
} else {
  // Choose a default value
  num_items = 10;

  // Remember the default value until the user chooses their own value
  persist_write_int(key, num_items);
}
```

The API provides a 'read' and 'write' function for each of these types,
with builtin data types retrieved through assignment, and complex ones into a
buffer provided by the app. Examples of each are shown below.

### Booleans

```c
uint32_t key = 0;
bool large_font_size = true;
```

```c
// Write a boolean value
persist_write_bool(key, large_font_size);
```

```c
// Read the boolean value
bool large_font_size = persist_read_bool(key);
```

### Integers

```c
uint32_t key = 1;
int highscore = 432;
```

```c
// Write an integer
persist_write_int(key, highscore);
```

```c
// Read the integer value
int highscore = persist_read_int(key);
```

### Strings

```c
uint32_t key = 2;
char *string = "Remember this!";
```

```c
// Write the string
persist_write_string(key, string);
```

```c
// Read the string
char buffer[32];
persist_read_string(key, buffer, sizeof(buffer));
```

### Data Structures

```c
typedef struct {
  int a;
  int b;
} Data;

uint32_t key = 3;
Data data = (Data) {
  .a = 32,
  .b = 45
};
```

```c
// Write the data structure
persist_write_data(key, &data, sizeof(Data));
```

```c
// Read the data structure
persist_read_data(key, &data, sizeof(Data));
```

> Note: If a persisted data structure's field layout changes between app
> versions, the data read may no longer be compatible (see below).

## Versioning Persisted Data

As already mentioned, automatic app updates will persist data between app
versions. However, if the format of persisted data changes in a new app version
(or keys change), developers should version their storage scheme and correctly
handle version changes appropriately.

One way to do this is to use an extra persisted integer as the storage scheme's
version number. If the scheme changes, simply update the version number and
migrate existing data as required. If old data cannot be migrated it should be
deleted and replaced with fresh data in the correct scheme from the user. An
example is shown below:

```c
const uint32_t storage_version_key = 786;
const int current_storage_version = 2;
```

```c
// Store the current storage scheme version number
persist_write_int(storage_version_key, current_storage_version);
```

In this example, data stored in a key of `12` is now stored in a key of `13` due
to a new key being inserted higher up the list of key values.

```c
// The scheme has changed, increment the version number
const int current_storage_version = 3;
```

```c
static void migrate_storage_data() {
  // Check the last storage scheme version the app used
  int last_storage_version = persist_read_int(storage_version_key);

  if (last_storage_version == current_storage_version) {
    // No migration necessary
    return;
  }

  // Migrate data
  switch(last_storage_version) {
    case 0:
      // ...
      break;
    case 1:
      // ...
      break;
    case 2: {
      uint32_t old_highscore_key = 12;
      uint32_t new_highscore_key = 13;

      // Migrate to scheme version 3
      int highscore = persist_read_int(old_highscore_key);
      persist_write_int(new_highscore_key, highscore);

      // Delete old data
      persist_delete(old_highscore_key);
      break;
  }

  // Migration is complete, store the current storage scheme version number
  persist_write_int(storage_version_key, current_storage_version);
}
```

## Alternative Method

In addition to the ``Storage`` API, data can also be persisted using the
`localStorage` API in PebbleKit JS, and communicated with the watch over
``AppMessage`` when the app is lanched. However, this method uses more power and
fails if the watch is not connected to the phone.


---
# 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: Wakeups
description: |
  Using the Wakeup API to launch an app at some future time.
guide_group: events-and-services
order: 8
related_examples:
  - title: Tea Timer
    url: https://github.com/pebble-examples/feature-app-wakeup
related_docs:
  - Wakeup
  - AppLaunchReason
  - launch_reason
  - Timer
---

The ``Wakeup`` API allows developers to schedule an app launch in the future,
even if the app itself is closed in the meantime. A wakeup event is scheduled in
a similar manner to a ``Timer`` with a future timestamp calculated beforehand.

## Calculating Timestamps

To schedule a wakeup event, first determine the timestamp of the desired wakeup
time as a `time_t` variable. Most uses of the ``Wakeup`` API will fall into
three distinct scenarios discussed below.

### A Future Time

Call ``time()`` and add the offset, measured in seconds. For example, for 30
minutes in the future:

```c
// 30 minutes from now
time_t timestamp = time(NULL) + (30 * SECONDS_PER_MINUTE);
```

### A Specific Time

Use ``clock_to_timestamp()`` to obtain a `time_t` timestamp by specifying a day
of the week and hours and minutes (in 24 hour format). For example, for the next
occuring Monday at 5 PM:

```c
// Next occuring monday at 17:00
time_t timestamp = clock_to_timestamp(MONDAY, 17, 0);
```

### Using a Timestamp Provided by a Web Service

The timestamp will need to be translated using the
[`getTimezoneOffset()`](http://www.w3schools.com/jsref/jsref_gettimezoneoffset.asp)
method available in PebbleKit JS or with any timezone information given by the
web service.

## Scheduling a Wakeup

Once a `time_t` timestamp has been calculated, the wakeup event can be
scheduled:

```c
// Let the timestamp be 30 minutes from now
const time_t future_timestamp = time() + (30 * SECONDS_PER_MINUTE);

// Choose a 'cookie' value representing the reason for the wakeup
const int cookie = 0;

// If true, the user will be notified if they missed the wakeup
// (i.e. their watch was off)
const bool notify_if_missed = true;

// Schedule wakeup event
WakeupId id = wakeup_schedule(future_timestamp, cookie, notify_if_missed);

// Check the scheduling was successful
if(id >= 0) {
  // Persist the ID so that a future launch can query it
  const wakeup_id_key = 43;
  persist_write_int(wakeup_id_key, id);
}
```

After scheduling a wakeup event it is possible to perform some interaction with
it. For example, reading the timestamp for when the event will occur using the
``WakeupId`` with ``wakeup_query()``, and then perform simple arithmetic to get
the time remaining:

```c
// This will be set by wakeup_query()
time_t wakeup_timestamp = 0;

// Is the wakeup still scheduled?
if(wakeup_query(id, &wakeup_timestamp)) {
  // Get the time remaining
  int seconds_remaining = wakeup_timestamp - time(NULL);
  APP_LOG(APP_LOG_LEVEL_INFO, "%d seconds until wakeup", seconds_remaining);
}
```

To cancel a scheduled event, use the ``WakeupId`` obtained when it was
scheduled:

```c
// Cancel a wakeup
wakeup_cancel(id);
```

To cancel all scheduled wakeup events:

```c
// Cancel all wakeups
wakeup_cancel_all();
```

## Limitations

There are three limitations that should be taken into account when using
the Wakeup API:

* There can be no more than 8 scheduled wakeup events per app at any one time.

* Wakeup events cannot be scheduled within 30 seconds of the current time.

* Wakeup events are given a one minute window either side of the wakeup time. In
  this time no app may schedule an event. The return ``StatusCode`` of
  ``wakeup_schedule()`` should be checked to determine whether the scheduling of
  the new event should be reattempted. A negative value indicates that the
  wakeup could not be scheduled successfully.

The possible ``StatusCode`` values are detailed below:

|StatusCode|Value|Description|
|----------|-----|-----------|
| `E_RANGE` | `-8` | The wakeup event cannot be scheduled due to another event in that period. |
| `E_INVALID_ARGUMENT` | `-4` | The time requested is in the past. |
| `E_OUT_OF_RESOURCES` | `-7` | The application has already scheduled all 8 wakeup events. |
| `E_INTERNAL` | `-3` | A system error occurred during scheduling. |

## Handling Wakeup Events

A wakeup event can occur at two different times - when the app is closed, and
when it is already launched and in the foreground.

If the app is launched due to a previously scheduled wakeup event, check the
``AppLaunchReason`` and load the app accordingly:

```c
static void init() {
  if(launch_reason() == APP_LAUNCH_WAKEUP) {
    // The app was started by a wakeup event.
    WakeupId id = 0;
    int32_t reason = 0;

    // Get details and handle the event appropriately
    wakeup_get_launch_event(&id, &reason);
  }

  /* other init code */

}
```

If the app is expecting a wakeup to occur while it is open, use a subscription
to the wakeup service to be notified of such events:

```c
static void wakeup_handler(WakeupId id, int32_t reason) {
  // A wakeup event has occurred while the app was already open
}
```

```c
// Subscribe to wakeup service
wakeup_service_subscribe(wakeup_handler);
```

The two approaches can also be combined for a unified response to being woken
up, not depenent on the state of the app:

```c
// Get details of the wakeup
wakeup_get_launch_event(&id, &reason);

// Manually handle using the handler
wakeup_handler(id, reason);
```


---
# 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: Animations
description: |
  How to use Animations and Timers to add life to your app.
guide_group: graphics-and-animations
order: 0
related_docs:
  - Animation
  - Timer
  - AnimationImplementation
related_examples:
  - title: Composite Animations Example
    url: https://github.com/pebble-examples/composite-animations-example
  - title: Feature Property Animation
    url: https://github.com/pebble-examples/feature-property-animation
---

The ``Animation`` API allows a variety of different types of value to be
smoothly animated from an initial value to a new value over time. Animations can
also use built-in easing curves to affect how the transition behaves.

## Using PropertyAnimations

The most common use of animations is to move a ``Layer`` (or similar) around the
display. For example, to show or hide some information or animate the time
changing in a watchface.

The simplest method of animating a ``Layer`` (such as a ``TextLayer``) is to use
a ``PropertyAnimation``, which animates a property of the target object. In this
example, the target is the frame property, which is a ``GRect`` To animate the
this property, ``property_animation_create_layer_frame()`` is used, which is a
convenience ``PropertyAnimation`` implementation provided by the SDK.

```c
static Layer *s_layer;
```

Create the Layer during ``Window`` initialization:

```c
// Create the Layer
s_layer = layer_create(some_bounds);
```

Determine the start and end values of the ``Layer``'s frame. These are the
'from' and 'to' locations and sizes of the ``Layer`` before and after the
animation takes place:

```c
// The start and end frames - move the Layer 40 pixels to the right
GRect start = GRect(10, 10, 20, 20);
GRect finish = GRect(50, 10, 20, 20);
```

At the appropriate time, create a ``PropertyAnimation`` to animate the
``Layer``, specifying the `start` and `finish` values as parameters:

```c
// Animate the Layer
PropertyAnimation *prop_anim = property_animation_create_layer_frame(s_layer,
                                                               &start, &finish);
```

Configure the attributes of the ``Animation``, such as the delay before
starting, and total duration (in milliseconds):

```c
// Get the Animation
Animation *anim = property_animation_get_animation(prop_anim);

// Choose parameters
const int delay_ms = 1000;
const int duration_ms = 500;

// Configure the Animation's curve, delay, and duration
animation_set_curve(anim, AnimationCurveEaseOut);
animation_set_delay(anim, delay_ms);
animation_set_duration(anim, duration_ms);
```

Finally, schedule the ``Animation`` to play at the next possible opportunity
(usually immediately):

```c
// Play the animation
animation_schedule(anim);
```

If the app requires knowledge of the start and end times of an ``Animation``, it
is possible to register ``AnimationHandlers`` to be notified of these events.
The handlers should be created with the signature of these examples shown below:

```c
static void anim_started_handler(Animation *animation, void *context) {
  APP_LOG(APP_LOG_LEVEL_DEBUG, "Animation started!");
}

static void anim_stopped_handler(Animation *animation, bool finished, void *context) {
  APP_LOG(APP_LOG_LEVEL_DEBUG, "Animation stopped!");
}
```

Register the handlers with an optional third context parameter **before**
scheduling the ``Animation``:

```c
// Set some handlers
animation_set_handlers(anim, (AnimationHandlers) {
  .started = anim_started_handler,
  .stopped = anim_stopped_handler
}, NULL);
```

With the handlers registered, the start and end times of the ``Animation`` can
be detected by the app and used as appropriate.

### Other Types of PropertyAnimation

In addition to ``property_animation_create_layer_frame()``, it is also possible
to animate the origin of a ``Layer``'s bounds using
``property_animation_create_bounds_origin()``. Animation of more types of data
can be achieved using custom implementations and one the following provided
update implementations and the associated
[getters and setters](``property_animation_update_int16``):

* ``property_animation_update_int16`` - Animate an `int16`.
* ``property_animation_update_uint32`` - Animate a `uint32`.
* ``property_animation_update_gpoint`` - Animate a ``GPoint``.
* ``property_animation_update_grect`` - Animate a ``GRect``
* ``property_animation_update_gcolor8`` - Animate a ``GColor8``.

## Custom Animation Implementations

Beyond the convenience functions provided by the SDK, apps can implement their
own ``Animation`` by using custom callbacks for each stage of the animation
playback process. A ``PropertyAnimation`` is an example of such an
implementation.

The callbacks to implement are the `.setup`, `.update`, and `.teardown` members
of an ``AnimationImplementation`` object. Some example implementations are shown
below. It is in the `.update` callback where the value of `progress` can be used
to modify the custom target of the animation. For example, some percentage of
completion:

```c
static void implementation_setup(Animation *animation) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Animation started!");
}

static void implementation_update(Animation *animation,
                                  const AnimationProgress progress) {
  // Animate some completion variable
  s_animation_percent = ((int)progress * 100) / ANIMATION_NORMALIZED_MAX;

  APP_LOG(APP_LOG_LEVEL_INFO, "Animation progress: %d%%", s_animation_percent);
}

static void implementation_teardown(Animation *animation) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Animation finished!");
}
```

Once these are in place, create a new ``Animation`` , specifying the new custom
implementation as a `const` object pointer at the appropriate time:

```c
// Create a new Animation
Animation *animation = animation_create();
animation_set_delay(animation, 1000);
animation_set_duration(animation, 1000);

// Create the AnimationImplementation
const AnimationImplementation implementation = {
  .setup = implementation_setup,
  .update = implementation_update,
  .teardown = implementation_teardown
};
animation_set_implementation(animation, &implementation);

// Play the Animation
animation_schedule(animation);
```

The output of the example above will look like the snippet shown below (edited
for brevity). Note the effect of the easing ``AnimationCurve`` on the progress
value:

```nc|text
[13:42:33] main.c:11> Animation started!
[13:42:34] main.c:19> Animation progress: 0%
[13:42:34] main.c:19> Animation progress: 0%
[13:42:34] main.c:19> Animation progress: 0%
[13:42:34] main.c:19> Animation progress: 2%
[13:42:34] main.c:19> Animation progress: 3%
[13:42:34] main.c:19> Animation progress: 5%
[13:42:34] main.c:19> Animation progress: 7%
[13:42:34] main.c:19> Animation progress: 10%
[13:42:34] main.c:19> Animation progress: 14%
[13:42:35] main.c:19> Animation progress: 17%
[13:42:35] main.c:19> Animation progress: 21%
[13:42:35] main.c:19> Animation progress: 26%

...

[13:42:35] main.c:19> Animation progress: 85%
[13:42:35] main.c:19> Animation progress: 88%
[13:42:35] main.c:19> Animation progress: 91%
[13:42:35] main.c:19> Animation progress: 93%
[13:42:35] main.c:19> Animation progress: 95%
[13:42:35] main.c:19> Animation progress: 97%
[13:42:35] main.c:19> Animation progress: 98%
[13:42:35] main.c:19> Animation progress: 99%
[13:42:35] main.c:19> Animation progress: 99%
[13:42:35] main.c:19> Animation progress: 100%
[13:42:35] main.c:23> Animation finished!
```

## Timers

[`AppTimer`](``Timer``) objects can be used to schedule updates to variables and
objects at a later time. They can be used to implement frame-by-frame animations
as an alternative to using the ``Animation`` API. They can also be used in a
more general way to schedule events to occur at some point in the future (such
as UI updates) while the app is open.

A thread-blocking alternative for small pauses is ``psleep()``, but this is
**not** recommended for use in loops updating UI (such as a counter), or for
scheduling ``AppMessage`` messages, which rely on the event loop to do their
work.

> Note: To create timed events in the future that persist after an app is
> closed, check out the ``Wakeup`` API.

When a timer elapses, it will call a developer-defined ``AppTimerCallback``.
This is where the code to be executed after the timed interval should be placed.
The callback will only be called once, so use this opportunity to re-register
the timer if it should repeat.

```c
static void timer_callback(void *context) {
  APP_LOG(APP_LOG_LEVEL_INFO, "Timer elapsed!");
}
```

Schedule the timer with a specific `delay` interval, the name of the callback to
fire, and an optional context pointer:

```c
const int delay_ms = 5000;

// Schedule the timer
app_timer_register(delay_ms, timer_callback, NULL);
```

If the timer may need to be cancelled or rescheduled at a later time, ensure a
reference to it is kept for later use:

```c
static AppTimer *s_timer;
```

```c
// Register the timer, and keep a handle to it
s_timer = app_timer_register(delay_ms, timer_callback, NULL);
```

If the timer needs to be cancelled, use the previous reference. If it has
already elapsed, nothing will occur:

```c
// Cancel the timer
app_timer_cancel(s_timer);
```

## Sequence and Spawn Animations

The Pebble SDK also includes the capability to build up composite animations
built from other ``Animation`` objects. There are two types: a
sequence animation and a spawn animation.

* A sequence animation is a set of two or more other animations that are played
  out in **series** (one after another). For example, a pair of timed animations
  to show and hide a ``Layer``.

* A spawn animation is a set of two or more other animations that are played out
  in **parallel**. A spawn animation acts the same as creating and starting two
  or more animations at the same time, but has the advantage that it can be
  included as part of a sequence animation.

> Note: Composite animations can be composed of other composite animations.

### Important Considerations

When incorporating an ``Animation`` into a sequence or spawn animation, there
are a couple of points to note:

* Any single animation cannot appear more than once in the list of animations
  used to create a more complex animation.

* A composite animation assumes ownership of its component animations once it
  has been created.

* Once an animation has been added to a composite animation, it becomes
  immutable. This means it can only be read, and not written to. Attempts to
  modify such an animation after it has been added to a composite animation will
  fail.

* Once an animation has been added to a composite animation, it cannot then be
  used to build a different composite animation.

### Creating a Sequence Animation

To create a sequence animation, first create the component ``Animation`` objects
that will be used to build it.

```c
// Create the first Animation
PropertyAnimation *prop_anim = property_animation_create_layer_frame(s_layer,
                                                               &start, &finish);
Animation *animation_a = property_animation_get_animation(prop_anim);

// Set some properties
animation_set_delay(animation_a, 1000);
animation_set_duration(animation_a, 500);

// Clone the first, modify the duration and reverse it.
Animation *animation_b = animation_clone(animation_a);
animation_set_reverse(animation_b, true);
animation_set_duration(animation_b, 1000);
```

Use these component animations to create the sequence animation. You can either
specify the components as a list or pass an array. Both approaches are shown
below.

#### Using a List

You can specify up to 20 ``Animation`` objects as parameters to
`animation_sequence_create()`. The list must always be terminated with `NULL` to
mark the end.

```c
// Create the sequence
Animation *sequence = animation_sequence_create(animation_a, animation_b, NULL);

// Play the sequence
animation_schedule(sequence);
```

#### Using an Array

You can also specify the component animations using a dynamically allocated
array. Give this to `animation_sequence_create_from_array()` along with the size
of the array.

```c
const uint32_t array_length = 2;

// Create the array
Animation **arr = (Animation**)malloc(array_length * sizeof(Animation*));
arr[0] = animation_a;
arr[1] = animation_b;

// Create the sequence, set to loop forever
Animation *sequence = animation_sequence_create_from_array(arr, array_length);
animation_set_play_count(sequence, ANIMATION_DURATION_INFINITE);

// Play the sequence
animation_schedule(sequence);

// Destroy the array
free(arr);
```

### Creating a Spawn Animation

Creating a spawn animation is done in a very similiar way to a sequence
animation. The animation is built up from component animations which are then
all started at the same time. This simplifies the task of precisely timing
animations that are designed to coincide.

The first step is the same as for sequence animations, which is to create a
number of component animations to be spawned together.

```c
// Create the first animation
Animation *animation_a = animation_create();
animation_set_duration(animation_a, 1000);

// Clone the first, modify the duration and reverse it.
Animation *animation_b = animation_clone(animation_a);
animation_set_reverse(animation_b, true);
animation_set_duration(animation_b, 300);
```

Next, the spawn animation is created in a similar manner to the sequence
animation with a `NULL` terminated list of parameters:

```c
// Create the spawn animation
Animation *spawn = animation_spawn_create(animation_a, animation_b, NULL);

// Play the animation
animation_schedule(spawn);
```

Alternatively the spawn animation can be created with an array of ``Animation``
objects.

```c
const uint32_t array_length = 2;

// Create the array
Animation **arr = (Animation**)malloc(array_length * sizeof(Animation*));
arr[0] = animation_a;
arr[1] = animation_b;

// Create the sequence and set the play count to 3
Animation *spawn = animation_spawn_create_from_array(arr, array_length);
animation_set_play_count(spawn, 3);

// Play the spawn animation
animation_schedule(spawn);

// Destroy the array
free(arr);
```


---
# 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: Drawing Primitives, Images and Text
description: |
  How to draw primitive shapes, image, and text onto the Graphics Context.
guide_group: graphics-and-animations
order: 1
---

While ``Layer`` types such as ``TextLayer`` and ``BitmapLayer`` allow easy
rendering of text and bitmaps, more precise drawing can be achieved through the
use of the ``Graphics Context`` APIs. Custom drawing of primitive shapes such as
line, rectangles, and circles is also supported. Clever use of these functions
can remove the need to pre-prepare bitmap images for many UI elements and icons.

## Obtaining a Drawing Context

All custom drawing requires a ``GContext`` instance. These cannot be created,
and are only available inside a ``LayerUpdateProc``. This update procedure is
simply a function that is called when a ``Layer`` is to be rendered, and is
defined by the developer as opposed to the system. For example, a
``BitmapLayer`` is simply a ``Layer`` with a ``LayerUpdateProc`` abstracted away
for convenience by the SDK.

First, create the ``Layer`` that will have a custom drawing procedure:

```c
static Layer *s_canvas_layer;
```

Allocate the ``Layer`` during ``Window`` creation:

```c
GRect bounds = layer_get_bounds(window_get_root_layer(window));

// Create canvas layer
s_canvas_layer = layer_create(bounds);
```

Next, define the ``LayerUpdateProc`` according to the function specification:

```c
static void canvas_update_proc(Layer *layer, GContext *ctx) {
  // Custom drawing happens here!

}
```

Assign this procedure to the canvas layer and add it to the ``Window`` to make
it visible:

```c
// Assign the custom drawing procedure
layer_set_update_proc(s_canvas_layer, canvas_update_proc);

// Add to Window
layer_add_child(window_get_root_layer(window), s_canvas_layer);
```

From now on, every time the ``Layer`` needs to be redrawn (for example, if other
layer geometry changes), the ``LayerUpdateProc`` will be called to allow the
developer to draw it. It can also be explicitly marked for redrawing at the next
opportunity:

```c
// Redraw this as soon as possible
layer_mark_dirty(s_canvas_layer);
```

## Drawing Primitive Shapes

The ``Graphics Context`` API allows drawing and filling of lines, rectangles,
circles, and arbitrary paths. For each of these, the colors of the output can be
set using the appropriate function:

```c
// Set the line color
graphics_context_set_stroke_color(ctx, GColorRed);

// Set the fill color
graphics_context_set_fill_color(ctx, GColorBlue);
```

In addition, the stroke width and antialiasing mode can also be changed:

```c
// Set the stroke width (must be an odd integer value)
graphics_context_set_stroke_width(ctx, 5);

// Disable antialiasing (enabled by default where available)
graphics_context_set_antialiased(ctx, false);
```

### Lines

Drawing a simple line requires only the start and end positions, expressed as
``GPoint`` values:

```c
GPoint start = GPoint(10, 10);
GPoint end = GPoint(40, 60);

// Draw a line
graphics_draw_line(ctx, start, end);
```

### Rectangles

Drawing a rectangle requires a bounding ``GRect``, as well as other parameters
if it is to be filled:

```c
GRect rect_bounds = GRect(10, 10, 40, 60);

// Draw a rectangle
graphics_draw_rect(ctx, rect_bounds);

// Fill a rectangle with rounded corners
int corner_radius = 10;
graphics_fill_rect(ctx, rect_bounds, corner_radius, GCornersAll);
```

It is also possible to draw a rounded unfilled rectangle:

```c
// Draw outline of a rounded rectangle
graphics_draw_round_rect(ctx, rect_bounds, corner_radius);
```

### Circles

Drawing a circle requries its center ``GPoint`` and radius:

```c
GPoint center = GPoint(25, 25);
uint16_t radius = 50;

// Draw the outline of a circle
graphics_draw_circle(ctx, center, radius);

// Fill a circle
graphics_fill_circle(ctx, center, radius);
```

In addition, it is possble to draw and fill arcs. In these cases, the
``GOvalScaleMode`` determines how the shape is adjusted to fill the rectangle,
and the cartesian angle values are transformed to preserve accuracy:

```c
int32_t angle_start = DEG_TO_TRIGANGLE(0);
int32_t angle_end = DEG_TO_TRIGANGLE(45);

// Draw an arc
graphics_draw_arc(ctx, rect_bounds, GOvalScaleModeFitCircle, angle_start,
                                                                    angle_end);
```

Lastly, a filled circle with a sector removed can also be drawn in a similar
manner. The value of `inset_thickness` determines the inner inset size that is
removed from the full circle:

```c
uint16_t inset_thickness = 10;

// Fill a radial section of a circle
graphics_fill_radial(ctx, rect_bounds, GOvalScaleModeFitCircle, inset_thickness,
                                                        angle_start, angle_end);
```

For more guidance on using round elements in apps, watch the presentation given
at the 2015 Developer Retreat on
[developing for Pebble Time Round](https://www.youtube.com/watch?v=3a1V4n9HDvY).

## Bitmaps

Manually drawing ``GBitmap`` images with the ``Graphics Context`` API is a
simple task, and has much in common with the alternative approach of using a
``BitmapLayer`` (which provides additional convenience funcionality).

The first step is to load the image data from resources (read
{% guide_link app-resources/images %} to learn how to include images in a
Pebble project):

```c
static GBitmap *s_bitmap;
```

```c
// Load the image data
s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE);
```

When the appropriate ``LayerUpdateProc`` is called, draw the image inside the
desired rectangle:

> Note: Unlike ``BitmapLayer``, the image will be drawn relative to the
> ``Layer``'s origin, and not centered.

```c
// Get the bounds of the image
GRect bitmap_bounds = gbitmap_get_bounds(s_bitmap);

// Set the compositing mode (GCompOpSet is required for transparency)
graphics_context_set_compositing_mode(ctx, GCompOpSet);

// Draw the image
graphics_draw_bitmap_in_rect(ctx, s_bitmap, bitmap_bounds);
```

Once the image is no longer needed (i.e.: the app is exiting), free the data:

```c
// Destroy the image data
gbitmap_destroy(s_bitmap);
```

## Drawing Text

Similar to the ``TextLayer`` UI component, a ``LayerUpdateProc`` can also be
used to draw text. Advantages can include being able to draw in multiple fonts
with only one ``Layer`` and combining text with other drawing operations.

The first operation to perform inside the ``LayerUpdateProc`` is to get or load
the font to be used for drawing and set the text's color:

```c
// Load the font
GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
// Set the color
graphics_context_set_text_color(ctx, GColorBlack);
```

Next, determine the bounds that will guide the text's position and overflow
behavior. This can either be the size of the ``Layer``, or a more precise bounds
of the text itself. This information can be useful for drawing multiple text
items after one another with automatic spacing.

```c
char *text = "Example test string for the Developer Website guide!";

// Determine a reduced bounding box
GRect layer_bounds = layer_get_bounds(layer);
GRect bounds = GRect(layer_bounds.origin.x, layer_bounds.origin.y,
                     layer_bounds.size.w / 2, layer_bounds.size.h);

// Calculate the size of the text to be drawn, with restricted space
GSize text_size = graphics_text_layout_get_content_size(text, font, bounds,
                              GTextOverflowModeWordWrap, GTextAlignmentCenter);
```

Finally, the text can be drawn into the appropriate bounding rectangle:

```c
// Draw the text
graphics_draw_text(ctx, text, font, bounds, GTextOverflowModeWordWrap,
                                            GTextAlignmentCenter, NULL);
```


---
# 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: Framebuffer Graphics
description: |
  How to perform advanced drawing using direct framebuffer access.
guide_group: graphics-and-animations
order: 2
related_docs:
  - Graphics Context
  - GBitmap
  - GBitmapDataRowInfo
---

In the context of a Pebble app, the framebuffer is the data region used to store
the contents of the what is shown on the display. Using the ``Graphics Context``
API allows developers to draw primitive shapes and text, but at a slower speed
and with a restricted set of drawing patterns. Getting direct access to the
framebuffer allows arbitrary transforms, special effects, and other
modifications to be applied to the display contents, and allows drawing at a
much greater speed than standard SDK APIs.

## Accessing the Framebuffer

Access to the framebuffer can only be obtained during a ``LayerUpdateProc``,
when redrawing is taking place. When the time comes to update the associated
``Layer``, the framebuffer can be obtained as a ``GBitmap``:

```c
static void layer_update_proc(Layer *layer, GContext *ctx) {
  // Get the framebuffer
  GBitmap *fb = graphics_capture_frame_buffer(ctx);

  // Manipulate the image data...

  // Finally, release the framebuffer
  graphics_release_frame_buffer(ctx, fb);
}
```

> Note: Once obtained, the framebuffer **must** be released back to the app so
> that it may continue drawing.

The format of the data returned will vary by platform, as will the
representation of a single pixel, shown in the table below.

| Platform | Framebuffer Bitmap Format | Pixel Format |
|:--------:|---------------------------|--------------|
| Aplite | ``GBitmapFormat1Bit`` | One bit (black or white) |
| Basalt | ``GBitmapFormat8Bit`` | One byte (two bits per color) |
| Chalk | ``GBitmapFormat8BitCircular`` | One byte (two bits per color) |

## Modifying the Framebuffer Data

Once the framebuffer has been captured, the underlying data can be manipulated
on a row-by-row or even pixel-by-pixel basis. This data region can be obtained
using ``gbitmap_get_data()``, but the recommended approach is to make use of
``gbitmap_get_data_row_info()`` objects to cater for platforms (such as Chalk),
where not every row is of the same width. The ``GBitmapDataRowInfo`` object
helps with this by providing a `min_x` and `max_x` value for each `y` used to
build it.

To iterate over all rows and columns, safely avoiding those with irregular start
and end indices, use two nested loops as shown below. The implementation of
`set_pixel_color()` is shown in
[*Getting and Setting Pixels*](#getting-and-setting-pixels):

> Note: it is only necessary to call ``gbitmap_get_data_row_info()`` once per
> row. Calling it more often (such as for every pixel) will incur a sigificant
> speed penalty.

```c
GRect bounds = layer_get_bounds(layer);

// Iterate over all rows
for(int y = 0; y < bounds.size.h; y++) {
  // Get this row's range and data
  GBitmapDataRowInfo info = gbitmap_get_data_row_info(fb, y);

  // Iterate over all visible columns
  for(int x = info.min_x; x <= info.max_x; x++) {
    // Manipulate the pixel at x,y...
    const GColor random_color = (GColor){ .argb = rand() % 255 };

    // ...to be a random color
    set_pixel_color(info, GPoint(x, y), random_color);
  }
}
```

## Getting and Setting Pixels

To modify a pixel's value, simply set a new value at the appropriate position in
the `data` field of that row's ``GBitmapDataRowInfo`` object. This will modify
the underlying data, and update the display once the frame buffer is released.

This process will be different depending on the ``GBitmapFormat`` of the
captured framebuffer. On a color platform, each pixel is stored as a single
byte. However, on black and white platforms this will be one bit per byte. Using
``memset()`` to read or modify the correct pixel on a black and white display
requires a bit more logic, shown below:

```c
static GColor get_pixel_color(GBitmapDataRowInfo info, GPoint point) {
#if defined(PBL_COLOR)
  // Read the single byte color pixel
  return (GColor){ .argb = info.data[point.x] };
#elif defined(PBL_BW)
  // Read the single bit of the correct byte
  uint8_t byte = point.x / 8;
  uint8_t bit = point.x % 8;
  return byte_get_bit(&info.data[byte], bit) ? GColorWhite : GColorBlack;
#endif
}
```

Setting a pixel value is achieved in much the same way, with different logic
depending on the format of the framebuffer on each platform:

```c
static void set_pixel_color(GBitmapDataRowInfo info, GPoint point,
                                                                GColor color) {
#if defined(PBL_COLOR)
  // Write the pixel's byte color
  memset(&info.data[point.x], color.argb, 1);
#elif defined(PBL_BW)
  // Find the correct byte, then set the appropriate bit
  uint8_t byte = point.x / 8;
  uint8_t bit = point.x % 8;
  byte_set_bit(&info.data[byte], bit, gcolor_equal(color, GColorWhite) ? 1 : 0);
#endif
}
```

The `byte_get_bit()` and `byte_set_bit()` implementations are shown here for
convenience:

```c
static bool byte_get_bit(uint8_t *byte, uint8_t bit) {
  return ((*byte) >> bit) & 1;
}

static void byte_set_bit(uint8_t *byte, uint8_t bit, uint8_t value) {
  *byte ^= (-value ^ *byte) & (1 << bit);
}
```

## Learn More

To see an example of what can be achieved with direct access to the framebuffer
and learn more about the underlying principles, watch the
[talk given at the 2014 Developer Retreat](https://www.youtube.com/watch?v=lYoHh19RNy4).

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


---
# 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: Graphics and Animations
description: |
  Information on using animations and drawing shapes, text, and images, as well
  as more advanced techniques.
guide_group: graphics-and-animations
menu: false
permalink: /guides/graphics-and-animations/
generate_toc: false
hide_comments: true
---

The Pebble SDK allows drawing of many types of graphics in apps. Using the
``Graphics Context``, ``GBitmap``, ``GDrawCommand`` and
[`Framebuffer`](``graphics_capture_frame_buffer``) APIs gives developers
complete control over the contents of the display, and also can be used to
complement UI created with the various types of ``Layer`` available to enable
more complex UI layouts.

Both image-based and property-based animations are available, as well as
scheduling regular updates to UI components with ``Timer`` objects. Creative use
of ``Animation`` and ``PropertyAnimation`` can help to delight and engage users,
as well as highlight important information inside apps.

## 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: Vector Graphics
description: |
  How to draw simple images using vector images, instead of bitmaps.
guide_group: graphics-and-animations
order: 3
platform_choice: true
related_docs:
  - Draw Commands
related_examples:
  - title: PDC Image
    url: https://github.com/pebble-examples/pdc-image
  - title: PDC Sequence
    url: https://github.com/pebble-examples/pdc-sequence
---

This is an overview of drawing vector images using Pebble Draw Command files.
See the [*Vector Animations*](/tutorials/advanced/vector-animations) tutorial
for more information.

## Vector Graphics on Pebble

As opposed to bitmaps which contain data for every pixel to be drawn, a vector
file contains only instructions about points contained in the image and how to
draw lines connecting them up. Instructions such as fill color, stroke color,
and stroke width are also included.

Vector images on Pebble are implemented using the ``Draw Commands`` API, which
allows apps to load and display PDC (Pebble Draw Command) images and sequences
that contain sets of these instructions. An example is the weather icon used in
weather timeline pins. The benefit of using vector graphics for this icon is
that is allows the image to stretch in the familiar manner as it moves between
the timeline view and the pin detail view:

![weather >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather.png)

The main benefits of vectors over bitmaps for simple images and icons are:

* Smaller resource size - instructions for joining points are less memory
  expensive than per-pixel bitmap data.

* Flexible rendering - vector images can be rendered as intended, or manipulated
  at runtime to move the individual points around. This allows icons to appear
  more organic and life-like than static PNG images. Scaling and distortion is
  also made possible.

However, there are also some drawbacks to choosing vector images in certain
cases:

* Vector files require more specialized tools to create than bitmaps, and so are
  harder to produce.

* Complicated vector files may take more time to render than if they were simply
  drawn per-pixel as a bitmap, depending on the drawing implementation.

## Creating Compatible Files

The file format of vector image files on Pebble is the PDC (Pebble Draw Command)
format, which includes all the instructions necessary to allow drawing of
vectors. These files are created from compatible SVG (Scalar Vector Graphics)
files. Read {% guide_link app-resources/converting-svg-to-pdc %} for more
information.

Pebble Draw Command files can only be used from app resources, and cannot be
created at runtime.

## Drawing Vector Graphics

^CP^ Add the PDC file as a project resource using the 'Add new' under
'Resources' on the left-hand side of the CloudPebble editor as a 'raw binary
blob'.

^LC^ Add the PDC file to the project resources in `package.json` with the
'type' field to `raw`:

{% highlight {} %}
"media": [
  {
    "type": "raw",
    "name": "EXAMPLE_IMAGE",
    "file": "example_image.pdc"
  }
]
{% endhighlight %}

^LC^ Drawing a Pebble Draw Command image is just as simple as drawing a normal
PNG image to a graphics context, requiring only one draw call. First, load the
`.pdc` file from resources as shown below.

^CP^ Drawing a Pebble Draw Command image is just as simple as drawing a normal
PNG image to a graphics context, requiring only one draw call. First, load the
`.pdc` file from resources, as shown below.

First, declare a pointer of type ``GDrawCommandImage`` at the top of the file:

```c
static GDrawCommandImage *s_command_image;
```

Create and assign the ``GDrawCommandImage`` in `init()`, before calling
`window_stack_push()`:

```nc|c
// Create the object from resource file
s_command_image = gdraw_command_image_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE);
```

Next, define the ``LayerUpdateProc`` that will be used to draw the PDC image:

```c
static void update_proc(Layer *layer, GContext *ctx) {
  // Set the origin offset from the context for drawing the image
  GPoint origin = GPoint(10, 20);

  // Draw the GDrawCommandImage to the GContext
  gdraw_command_image_draw(ctx, s_command_image, origin);
}
```

Next, create a ``Layer`` to display the image:

```c
static Layer *s_canvas_layer;
```

Assign the ``LayerUpdateProc`` that will do the rendering to the canvas
``Layer`` and add it to the desired ``Window`` during `window_load()`:

```c
// Create the canvas Layer
s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h));

// Set the LayerUpdateProc
layer_set_update_proc(s_canvas_layer, update_proc);

// Add to parent Window
layer_add_child(window_layer, s_canvas_layer);
```

Finally, don't forget to free the memory used by the sub-components of the
``Window`` in `main_window_unload()`:

```c
// Destroy the canvas Layer
layer_destroy(s_canvas_layer);

// Destroy the PDC image
gdraw_command_image_destroy(s_command_image);
```

When run, the PDC image will be loaded, and rendered in the ``LayerUpdateProc``.
To put the image into contrast, optionally change the ``Window`` background
color after `window_create()`:

```c
window_set_background_color(s_main_window, GColorBlueMoon);
```

The result will look similar to the example shown below.

![weather-image >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather-image.png)


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

title: SDK 3.x on Aplite Migration Guide
description: How to migrate apps that use SDK 2.x on Aplite to SDK 3.x
permalink: /guides/migration/3x-aplite-migration/
generate_toc: true
guide_group: migration
order: 3
---

With the release of SDK 3.8, all Pebble platforms can be targeted with one SDK.
This enables developers to make use of lots of new APIs added since SDK 2.9, as
well as the countless bug fixes and improvements also added in the intervening
time. Some examples are:

* Timezone support

* Sequence and spawn animations

* Pebble timeline support

* Pebble Draw Commands

* Native PNG image support

* 8k `AppMessage` buffers

* New UI components such as `ActionMenu`, `StatusBarLayer`, and
  `ContentIndicator`.

* The stroke width, drawing arcs, polar points APIs, and the color gray!

## Get the Beta SDK

To try out the beta SDK, read the instructions on the [SDK Beta](/sdk/beta)
page.

## Mandatory Changes

To be compatible with users who update their Pebble Classic or Pebble Steel to
firmware 3.x the following important changes **MUST** be made:

* If you are adding support for Aplite, add `aplite` to your `targetPlatforms`
  array in `package.json`, or tick the 'Build Aplite' box in 'Settings' on
  CloudPebble.

* Recompile your app with at least Pebble SDK 3.8 (coming soon!). The 3.x on
  Aplite files will reside in `/aplite/` instead of the `.pbw` root folder.
  Frankenpbws are **not** encouraged - a 2.x compatible release can be uploaded
  separately (see [*Appstore Changes*](#appstore-changes)).

* Update any old practices such as direct struct member access. An example is
  shown below:

    ```c
    // 2.x - don't do this!
    GRect bitmap_bounds = s_bitmap->bounds;

    // 3.x - please do this!
    GRect bitmap_bounds = gbitmap_get_bounds(s_bitmap);
    ```

* Apps that make use of the `png-trans` resource type should now make use of
  built-in PNG support, which allows a single black and white image with
  transparency to be used in place of the older compositing technique.

* If your app uses either the ``Dictation`` or ``Smartstrap`` APIs, you must
  check that any code dependant on these hardware features fails gracefully when
  they are not available. This should be done by checking for `NULL` or
  appropriate `enum` values returned from affected API calls. An example is
  shown below:

    ```c
    if(smartstrap_subscribe(handlers) != SmartstrapResultNotPresent) {
      // OK to use Smartstrap API!
    } else {
      // Not available, handle gracefully
      text_layer_set_text(s_text_layer, "Smartstrap not available!");
    }

    DictationSession *session = dictation_session_create(size, callback, context);
    if(session) {
      // OK to use Dictation API!
    } else {
      // Not available, handle gracefully
      text_layer_set_text(s_text_layer, "Dictation not available!");
    }
    ```

## Appstore Changes

To handle the transition as users update their Aplite to firmware 3.x (or choose
not to), the appstore will include the following changes:

* You can now have multiple published releases. When you publish a new release,
  it doesn’t unpublish the previous one. You can still manually unpublish
  releases whenever they want.

* The appstore will provide the most recently compatible release of an app to
  users. This means that if you publish a new release that has 3.x Aplite
  support, the newest published release that supports 2.x Aplite will be
  provided to users on 2.x Aplite.

* There will be a fourth Asset Collection type that you can create: Legacy
  Aplite. Apps that have different UI designs between 2.x and 3.x on Aplite
  should use the Legacy Aplite asset collection for their 2.x assets.

## Suggested Changes

To fully migrate to SDK 3.x, we also suggest you make these nonessential
changes:

* Remove any code conditionally compiled with `PBL_SDK_2` defines. It will no
  longer be compiled at all.

* Ensure that any use of ``app_message_inbox_size_maximum()`` and
  ``app_message_outbox_size_maximum()`` does not cause your app to run out of
  memory. These calls now create ``AppMessage`` buffers of 8k size by default.
  Aplite apps limited to 24k of RAM will quickly run out if they use much more
  memory.

* Colors not available on the black and white Aplite display will be silently
  displayed as the closet match (black, or white). We recommend checking every
  instance of a `GColor`to ensure each is the correct one. `GColorDarkGray` and
  `GColorLightGray` will result in a 50/50 dithered gray for **fill**
  operations. All other line and text drawing will be mapped to the nearest
  solid color (black or white).

* Apps using image resources should take advantage of the new `bitmap` resource
  type, which optimizes image files for you. Read the
  [*Unifying Bitmap Resources*](/blog/2015/12/02/Bitmap-Resources/)
  blog post to learn more.

* In addition to the point above, investigate how the contrast and readability
  of your app can be improved by making use of gray. Examples of this can be
  seen in the system UI:

![3x-aplite-system >{pebble-screenshot,pebble-screenshot--black}](/images/guides/migration/3x-aplite-system.png)


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

title: Migrating Older Apps
description: |
  Details on how to update older apps affected by API changes.
guide_group: migration
menu: false
permalink: /guides/migration/
generate_toc: false
hide_comments: true
---

When the Pebble SDK major version is increased (such as from 2.x to 3.x), some
breaking API and build process changes are made. This means that some apps
written for an older SDK may no longer compile with the newer one.

To help developers transition their code, these guides detail the specific
changes they should look out for and highlighting the changes to APIs they may
have previously been using. When breaking changes are made in the future, new
guides will be added here to help developers make the required changes.

## Contents

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