mirror of
https://github.com/google/pebble.git
synced 2025-05-25 04:34:52 +00:00
Import the pebble dev site into devsite/
This commit is contained in:
parent
3b92768480
commit
527858cf4c
1359 changed files with 265431 additions and 0 deletions
|
@ -0,0 +1,373 @@
|
|||
---
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
title: Building for Every Pebble
|
||||
description: How to write one app compatible with all Pebble smartwatches.
|
||||
guide_group: best-practices
|
||||
order: 0
|
||||
---
|
||||
|
||||
The difference in capabilities between the various Pebble hardware platforms are
|
||||
listed in
|
||||
{% guide_link tools-and-resources/hardware-information %}. For example, the
|
||||
Basalt, Chalk and Emery platforms support 64 colors, whereas the Aplite and
|
||||
Diorite platforms only support two colors. This can make developing apps with
|
||||
rich color layouts difficult when considering compatibility with other non-color
|
||||
hardware. Another example is using platform specific APIs such as Health or
|
||||
Dictation.
|
||||
|
||||
To make life simple for users, developers should strive to write one app that
|
||||
can be used on all platforms. To help make this task simpler for developers, the
|
||||
Pebble SDK provides numerous methods to accommodate different hardware
|
||||
capabilities in code.
|
||||
|
||||
|
||||
## Preprocessor Directives
|
||||
|
||||
It is possible to specify certain blocks of code to be compiled for specific
|
||||
purposes by using the `#ifdef` preprocessor statement. For example, the
|
||||
``Dictation`` API should be excluded on platforms with no microphone:
|
||||
|
||||
```c
|
||||
#if defined(PBL_MICROPHONE)
|
||||
// Start dictation UI
|
||||
dictation_session_start(s_dictation_session);
|
||||
#else
|
||||
// Microphone is not available
|
||||
text_layer_set_text(s_some_layer, "Dictation not available!");
|
||||
#endif
|
||||
```
|
||||
|
||||
When designing UI layouts, any use of colors on compatible platforms can be
|
||||
adapted to either black or white on non-color platforms. The `PBL_COLOR` and
|
||||
`PBL_BW` symbols will be defined at compile time when appropriate capabilities
|
||||
are available:
|
||||
|
||||
```c
|
||||
#if defined(PBL_COLOR)
|
||||
text_layer_set_text_color(s_text_layer, GColorRed);
|
||||
text_layer_set_background_color(s_text_layer, GColorChromeYellow);
|
||||
#else
|
||||
text_layer_set_text_color(s_text_layer, GColorWhite);
|
||||
text_layer_set_background_color(s_text_layer, GColorBlack);
|
||||
#endif
|
||||
```
|
||||
|
||||
This is useful for blocks of multiple statements that change depending on the
|
||||
availability of color support. For single statements, this can also be achieved
|
||||
by using the ``PBL_IF_COLOR_ELSE()`` macro.
|
||||
|
||||
```c
|
||||
window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorJaegerGreen, GColorBlack));
|
||||
```
|
||||
|
||||
See below for a complete list of defines and macros available.
|
||||
|
||||
|
||||
## Available Defines and Macros
|
||||
|
||||
The tables below show a complete summary of all the defines and associated
|
||||
macros available to conditionally compile or omit feature-dependant code. The
|
||||
macros are well-suited for individual value selection, whereas the defines are
|
||||
better used to select an entire block of code.
|
||||
|
||||
| Define | MACRO |Available |
|
||||
|--------|-------|----------|
|
||||
| `PBL_BW` | `PBL_IF_BW_ELSE()` | Running on hardware that supports only black and white. |
|
||||
| `PBL_COLOR` | `PBL_IF_COLOR_ELSE()` | Running on hardware that supports 64 colors. |
|
||||
| `PBL_MICROPHONE` | `PBL_IF_MICROPHONE_ELSE()` | Running on hardware that includes a microphone. |
|
||||
| `PBL_COMPASS` | None | Running on hardware that includes a compass. |
|
||||
| `PBL_SMARTSTRAP` | `PBL_IF_SMARTSTRAP_ELSE()` | Running on hardware that includes a smartstrap connector, but does not indicate that the connector is capable of supplying power. |
|
||||
| `PBL_SMARTSTRAP_POWER` | None | Running on hardware that includes a smartstrap connector capable of supplying power. |
|
||||
| `PBL_HEALTH` | `PBL_IF_HEALTH_ELSE()` | Running on hardware that supports Pebble Health and the `HealthService` API. |
|
||||
| `PBL_RECT` | `PBL_IF_RECT_ELSE()` | Running on hardware with a rectangular display. |
|
||||
| `PBL_ROUND` | `PBL_IF_ROUND_ELSE()` | Running on hardware with a round display. |
|
||||
| `PBL_DISPLAY_WIDTH` | None | Determine the screen width in pixels. |
|
||||
| `PBL_DISPLAY_HEIGHT` | None | Determine the screen height in pixels. |
|
||||
| `PBL_PLATFORM_APLITE` | None | Built for Pebble/Pebble Steel. |
|
||||
| `PBL_PLATFORM_BASALT` | None | Built for Pebble Time/Pebble Time Steel. |
|
||||
| `PBL_PLATFORM_CHALK` | None | Built for Pebble Time Round. |
|
||||
| `PBL_PLATFORM_DIORITE` | None | Built for Pebble 2. |
|
||||
| `PBL_PLATFORM_EMERY` | None | Built for Pebble Time 2. |
|
||||
| `PBL_SDK_2` | None | Compiling with SDK 2.x (deprecated). |
|
||||
| `PBL_SDK_3` | None | Compiling with SDK 3.x. or 4.x. |
|
||||
|
||||
> Note: It is strongly recommended to conditionally compile code using
|
||||
> applicable feature defines instead of `PBL_PLATFORM` defines to be as specific
|
||||
> as possible.
|
||||
|
||||
## API Detection
|
||||
|
||||
In addition to platform and capabilities detection, we now provide API
|
||||
detection to detect if a specific API method is available. This approach could
|
||||
be considered future-proof, since platforms and capabilities may come and go.
|
||||
Let's take a look at a simple example:
|
||||
|
||||
```c
|
||||
#if PBL_API_EXISTS(health_service_peek_current_value)
|
||||
// Do something if specific Health API exists
|
||||
#endif
|
||||
```
|
||||
|
||||
## Avoid Hardcoded Layout Values
|
||||
|
||||
With the multiple display shapes and resolutions available, developers should
|
||||
try and avoid hardcoding layout values. Consider the example
|
||||
below:
|
||||
|
||||
```c
|
||||
static void window_load(Window *window) {
|
||||
// Create a full-screen Layer - BAD
|
||||
s_some_layer = layer_create(GRect(0, 0, 144, 168));
|
||||
}
|
||||
```
|
||||
|
||||
The hardcoded width and height of this layer will cover the entire screen on
|
||||
Aplite, Basalt and Diorite, but not on Chalk or Emery. This kind of screen
|
||||
size-dependant calculation should use the ``UnobstructedArea`` bounds of the
|
||||
``Window`` itself:
|
||||
|
||||
```c
|
||||
static void window_load(Window *window) {
|
||||
// Get the unobstructed bounds of the Window
|
||||
Layer window_layer = window_get_root_layer(window);
|
||||
GRect window_bounds = layer_get_unobstructed_bounds(window_layer);
|
||||
|
||||
// Properly create a full-screen Layer - GOOD
|
||||
s_some_layer = layer_create(window_bounds);
|
||||
}
|
||||
```
|
||||
|
||||
Another common use of this sort of construction is to make a ``Layer`` that is
|
||||
half the unobstructed screen height. This can also be correctly achieved using
|
||||
the ``Window`` unobstructed bounds:
|
||||
|
||||
```c
|
||||
GRect layer_bounds = window_bounds;
|
||||
layer_bounds.size.h /= 2;
|
||||
|
||||
// Create a Layer that is half the screen height
|
||||
s_some_layer = layer_create(layer_bounds);
|
||||
```
|
||||
|
||||
This approach is also advantageous in simplifying updating an app for a future
|
||||
new screen size, as proportional layout values will adapt as appropriate when
|
||||
the ``Window`` unobstructed bounds change.
|
||||
|
||||
|
||||
## Screen Sizes
|
||||
|
||||
To ease the introduction of the Emery platform, the Pebble SDK introduced new
|
||||
compiler directives to allow developers to determine the screen width and
|
||||
height. This is preferable to using platform detection, since multiple platforms
|
||||
share the same screen width and height.
|
||||
|
||||
```c
|
||||
#if PBL_DISPLAY_HEIGHT == 228
|
||||
uint8_t offset_y = 100;
|
||||
#elif PBL_DISPLAY_HEIGHT == 180
|
||||
uint8_t offset_y = 80;
|
||||
#else
|
||||
uint8_t offset_y = 60;
|
||||
#endif
|
||||
```
|
||||
|
||||
> Note: Although this method is preferable to platform detection, it is better
|
||||
to dynamically calculate the display width and height based on the unobstructed
|
||||
bounds of the root layer.
|
||||
|
||||
## Pebble C WatchInfo
|
||||
|
||||
The ``WatchInfo`` API can be used to determine exactly which Pebble model and
|
||||
color an app is running on. Apps can use this information to dynamically
|
||||
modify their layout or behavior depending on which Pebble the user is wearing.
|
||||
|
||||
For example, the display on Pebble Steel is located at a different vertical
|
||||
position relative to the buttons than on Pebble Time. Any on-screen button hints
|
||||
can be adjusted to compensate for this using ``WatchInfoModel``.
|
||||
|
||||
```c
|
||||
static void window_load(Window *window) {
|
||||
Layer window_layer = window_get_root_layer(window);
|
||||
GRect window_bounds = layer_get_bounds(window_layer);
|
||||
|
||||
int button_height, y_offset;
|
||||
|
||||
// Conditionally set layout parameters
|
||||
switch(watch_info_get_model()) {
|
||||
case WATCH_INFO_MODEL_PEBBLE_STEEL:
|
||||
y_offset = 64;
|
||||
button_height = 44;
|
||||
break;
|
||||
case WATCH_INFO_MODEL_PEBBLE_TIME:
|
||||
y_offset = 58;
|
||||
button_height = 56;
|
||||
break;
|
||||
|
||||
/* Other cases */
|
||||
|
||||
default:
|
||||
y_offset = 0;
|
||||
button_height = 0;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Set the Layer frame
|
||||
GRect layer_frame = GRect(0, y_offset, window_bounds.size.w, button_height);
|
||||
|
||||
// Create the Layer
|
||||
s_label_layer = text_layer_create(layer_frame);
|
||||
layer_add_child(window_layer, text_layer_get_layer(s_label_layer));
|
||||
|
||||
/* Other UI code */
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Developers can also use ``WatchInfoColor`` values to theme an app for each
|
||||
available color of Pebble.
|
||||
|
||||
```c
|
||||
static void window_load(Window *window) {
|
||||
GColor text_color, background_color;
|
||||
|
||||
// Choose different theme colors per watch color
|
||||
switch(watch_info_get_color()) {
|
||||
case WATCH_INFO_COLOR_RED:
|
||||
// Red theme
|
||||
text_color = GColorWhite;
|
||||
background_color = GColorRed;
|
||||
break;
|
||||
case WATCH_INFO_COLOR_BLUE:
|
||||
// Blue theme
|
||||
text_color = GColorBlack;
|
||||
background_color = GColorVeryLightBlue;
|
||||
break;
|
||||
|
||||
/* Other cases */
|
||||
|
||||
default:
|
||||
text_color = GColorBlack;
|
||||
background_color = GColorWhite;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Use the conditionally set value
|
||||
text_layer_set_text_color(s_label_layer, text_color);
|
||||
text_layer_set_background_color(s_label_layer, background_color);
|
||||
|
||||
/* Other UI code */
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## PebbleKit JS Watch Info
|
||||
|
||||
Similar to [*Pebble C WatchInfo*](#pebble-c-watchinfo) above, the PebbleKit JS
|
||||
``Pebble.getActiveWatchInfo()`` method allows developers to determine
|
||||
which model and color of Pebble the user is wearing, as well as the firmware
|
||||
version running on it. For example, to obtain the model of the watch:
|
||||
|
||||
> Note: See the section below to avoid problem using this function on older app
|
||||
> version.
|
||||
|
||||
```js
|
||||
// Get the watch info
|
||||
var info = Pebble.getActiveWatchInfo();
|
||||
|
||||
console.log('Pebble model: ' + info.model);
|
||||
```
|
||||
|
||||
|
||||
## Detecting Platform-specific JS Features
|
||||
|
||||
A number of features in PebbleKit JS (such as ``Pebble.timelineSubscribe()`` and
|
||||
``Pebble.getActiveWatchInfo()``) exist on SDK 3.x. If an app tries to use any of
|
||||
these on an older Pebble mobile app version where they are not available, the JS
|
||||
app will crash.
|
||||
|
||||
To prevent this, be sure to check for the availability of the function before
|
||||
calling it. For example, in the case of ``Pebble.getActiveWatchInfo()``:
|
||||
|
||||
```js
|
||||
if (Pebble.getActiveWatchInfo) {
|
||||
// Available.
|
||||
var info = Pebble.getActiveWatchInfo();
|
||||
|
||||
console.log('Pebble model: ' + info.model);
|
||||
} else {
|
||||
// Gracefully handle no info available
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Platform-specific Resources
|
||||
|
||||
With the availability of color support on Basalt, Chalk and Emery, developers
|
||||
may wish to include color versions of resources that had previously been
|
||||
pre-processed for Pebble's black and white display. Including both versions of
|
||||
the resource is expensive from a resource storage perspective, and lays the
|
||||
burden of packing redundant color resources in an Aplite or Diorite app when
|
||||
built for multiple platforms.
|
||||
|
||||
To solve this problem, the Pebble SDK allows developers to specify which version
|
||||
of an image resource is to be used for each display type, using `~bw` or
|
||||
`~color` appended to a file name. Resources can also be bundled only with
|
||||
specific platforms using the `targetPlatforms` property for each resource.
|
||||
|
||||
For more details about packaging resources specific to each platform, as well as
|
||||
more tags available similar to `~color`, read
|
||||
{% guide_link app-resources/platform-specific %}.
|
||||
|
||||
|
||||
## Multiple Display Shapes
|
||||
|
||||
With the introduction of the Chalk platform, a new round display type is
|
||||
available with increased pixel resolution. To distinguish between the two
|
||||
possible shapes of display, developers can use defines to conditionally
|
||||
include code segments:
|
||||
|
||||
```c
|
||||
#if defined(PBL_RECT)
|
||||
printf("This is a rectangular display!");
|
||||
#elif defined(PBL_ROUND)
|
||||
printf("This is a round display!");
|
||||
#endif
|
||||
```
|
||||
|
||||
Another approach to this conditional compilation is to use the
|
||||
``PBL_IF_RECT_ELSE()`` and ``PBL_IF_ROUND_ELSE()`` macros, allowing values to be
|
||||
inserted into expressions that might otherwise require a set of `#define`
|
||||
statements similar to the previous example. This would result in needless
|
||||
verbosity of four extra lines of code when only one is actually needed. These
|
||||
are used in the following manner:
|
||||
|
||||
```c
|
||||
// Conditionally print out the shape of the display
|
||||
printf("This is a %s display!", PBL_IF_RECT_ELSE("rectangular", "round"));
|
||||
```
|
||||
|
||||
This mechanism is best used with window bounds-derived layout size and position
|
||||
value. See the [*Avoid Hardcoded Layout Values*](#avoid-hardcoded-layout-values)
|
||||
section above for more information. Making good use of the builtin ``Layer``
|
||||
types will also help safeguard apps against display shape and size changes.
|
||||
|
||||
Another thing to consider is rendering text on a round display. Due to the
|
||||
rounded corners, each horizontal line of text will have a different available
|
||||
width, depending on its vertical position.
|
241
devsite/source/_guides/best-practices/conserving-battery-life.md
Normal file
241
devsite/source/_guides/best-practices/conserving-battery-life.md
Normal file
|
@ -0,0 +1,241 @@
|
|||
---
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
title: Conserving Battery Life
|
||||
description: How to write an app to consume power as efficiently as possible.
|
||||
guide_group: best-practices
|
||||
order: 1
|
||||
related_docs:
|
||||
- Animation
|
||||
- Timer
|
||||
- AccelerometerService
|
||||
- BatteryStateService
|
||||
- TickTimerService
|
||||
- CompassService
|
||||
- Vibes
|
||||
- Light
|
||||
related_examples:
|
||||
- title: Pebble Glancing Demo
|
||||
url: https://github.com/pebble-hacks/pebble_glancing_demo
|
||||
---
|
||||
|
||||
One of Pebble's strengths is its long battery life. This is due in part to using
|
||||
a low-power display technology, conservative use of the backlight, and allowing
|
||||
the processor to sleep whenever possible. It therefore follows that apps which
|
||||
misuse high-power APIs or prevent power-saving mechanisms from working will
|
||||
detract from the user's battery life. Several common causes of battery drain in
|
||||
apps are discussed in this guide, alongside suggestions to help avoid them.
|
||||
|
||||
|
||||
## Battery Ratings
|
||||
|
||||
Any app published in the [Developer Portal](https://dev-portal.getpebble.com)
|
||||
will have a battery grade associated with it, once a minimum threshold of data
|
||||
has been collected. This can be used to get a rough idea of how much battery
|
||||
power the app consumes. For watchfaces and apps that will be launched for long
|
||||
periods of time, making sure this grade is in the A - C range should be a
|
||||
priority. Read {% guide_link appstore-publishing/appstore-analytics %} to learn
|
||||
more about this rating system.
|
||||
|
||||
|
||||
## Time Awake
|
||||
|
||||
Because the watch tries to sleep as much as possible to conserve power, any app
|
||||
that keeps the watch awake will incur significant a battery penalty. Examples of
|
||||
such apps include those that frequently use animations, sensors, Bluetooth
|
||||
communications, and vibrations.
|
||||
|
||||
|
||||
### Animations and Display Updates
|
||||
|
||||
A common cause of such a drain are long-running animations that cause frequent
|
||||
display updates. For example, a watchface that plays a half-second ``Animation``
|
||||
for every second that ticks by will drain the battery faster than one that does
|
||||
so only once per minute. The latter approach will allow a lot more time for the
|
||||
watch to sleep.
|
||||
|
||||
```c
|
||||
static void tick_handler(struct tm *tick_time, TimeUnits changed) {
|
||||
// Update time
|
||||
set_time_digits(tick_time);
|
||||
|
||||
// Only update once a minute
|
||||
if(tick_time->tm_sec == 0) {
|
||||
play_animation();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This also applies to apps that make use of short-interval ``Timer``s, which is
|
||||
another method of creating animations. Consider giving users the option to
|
||||
reduce or disable animations to further conserve power, as well as removing or
|
||||
shortening animations that are not essential to the app's function or aesthetic.
|
||||
|
||||
However, not all animations are bad. Efficient use of the battery can be
|
||||
maintained if the animations are played at more intelligent times. For example,
|
||||
when the user is holding their arm to view the screen (see
|
||||
[`pebble_glancing_demo`](https://github.com/pebble-hacks/pebble_glancing_demo))
|
||||
or only when a tap or wrist shake is detected:
|
||||
|
||||
```c
|
||||
static void accel_tap_handler(AccelAxisType axis, int32_t direction) {
|
||||
// Animate when the user flicks their wrist
|
||||
play_animation();
|
||||
}
|
||||
```
|
||||
|
||||
```c
|
||||
accel_tap_service_subscribe(tap_handler);
|
||||
```
|
||||
|
||||
|
||||
### Tick Updates
|
||||
|
||||
Many watchfaces unecessarily tick once a second by using the ``SECOND_UNIT``
|
||||
constant value with the ``TickTimerService``, when they only update the display
|
||||
once a minute. By using the ``MINUTE_UNIT`` instead, the amount of times the
|
||||
watch is woken up per minute is reduced.
|
||||
|
||||
```c
|
||||
// Only tick once a minute, much more time asleep
|
||||
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
|
||||
```
|
||||
|
||||
If possible, give users the choice to disable the second hand tick and/or
|
||||
animation to further save power. Extremely minimal watchfaces may also use the
|
||||
``HOUR_UNIT`` value to only be updated once per hour.
|
||||
|
||||
This factor is especially important for Pebble Time Round users. On this
|
||||
platform the reduced battery capacity means that a watchface with animations
|
||||
that play every second could reduce this to one day or less. Consider offering
|
||||
configuration options to reducing tick updates on this platform to save power
|
||||
where it at a premium.
|
||||
|
||||
|
||||
### Sensor Usage
|
||||
|
||||
Apps that make frequent usage of Pebble's onboard accelerometer and compass
|
||||
sensors will also prevent the watch from going to sleep and consume more battery
|
||||
power. The ``AccelerometerService`` API features the ability to configure the
|
||||
sampling rate and number of samples received per update, allowing batching of
|
||||
data into less frequent updates. By receiving updates less frequently, the
|
||||
battery will last longer.
|
||||
|
||||
```c
|
||||
// Batch samples into sets of 10 per callback
|
||||
const uint32_t num_samples = 10;
|
||||
|
||||
// Sample at 10 Hz
|
||||
accel_service_set_sampling_rate(ACCEL_SAMPLING_10HZ);
|
||||
|
||||
// With this combination, only wake up the app once per second!
|
||||
accel_data_service_subscribe(num_samples, accel_data_handler);
|
||||
```
|
||||
|
||||
Similarly, the ``CompassService`` API allows a filter to be set on the heading
|
||||
updates, allowing an app to only be notified per every 45 degree angle change,
|
||||
for example.
|
||||
|
||||
```c
|
||||
// Only update if the heading changes significantly
|
||||
compass_service_set_heading_filter(TRIG_MAX_ANGLE / 36);
|
||||
```
|
||||
|
||||
In addition, making frequent use of the ``Dictation`` API will also keep the
|
||||
watch awake, and also incur a penalty for keeping the Bluetooth connection
|
||||
alive. Consider using the ``Storage`` API to remember previous user input and
|
||||
instead present a list of previous inputs if appropriate to reduce usage of this
|
||||
API.
|
||||
|
||||
```c
|
||||
static void dictation_session_callback(DictationSession *session, DictationSessionStatus status,
|
||||
char *transcription, void *context) {
|
||||
if(status == DictationSessionStatusSuccess) {
|
||||
// Display the dictated text
|
||||
snprintf(s_last_text, sizeof(s_last_text), "Transcription:\n\n%s",
|
||||
transcription);
|
||||
text_layer_set_text(s_output_layer, s_last_text);
|
||||
|
||||
// Save for later!
|
||||
const int last_text_key = 0;
|
||||
persist_write_string(last_text_key, s_last_text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Bluetooth Usage
|
||||
|
||||
Hinted at above, frequent use of the ``AppMessage`` API to send and recieve data
|
||||
will cause the Bluetooth connection to enter a more responsive state, which
|
||||
consumes much more power. A small time after a message is sent, the connection
|
||||
will return back to a low-power state.
|
||||
|
||||
The 'sniff interval' determines how often the API checks for new messages from
|
||||
the phone, and should be let in the default ``SNIFF_INTERVAL_NORMAL`` state as
|
||||
much as possible. Consider how infrequent communication activities can be to
|
||||
save power and maintain functionality, and how data obtained over the Bluetooth
|
||||
connection can be cached using the ``Storage`` API to reduce the frequency of
|
||||
updates (for example, weather information in watchface).
|
||||
|
||||
If the reduced sniff state must be used to transfer large amounts of data
|
||||
quickly, be sure to return to the low-power state as soon as the transfer is
|
||||
complete:
|
||||
|
||||
```c
|
||||
// Return to low power Bluetooth state
|
||||
app_comm_set_sniff_interval(SNIFF_INTERVAL_NORMAL);
|
||||
```
|
||||
|
||||
|
||||
## Backlight Usage
|
||||
|
||||
The backlight LED is another large consumer of battery power. System-level
|
||||
backlight settings may see the backlight turn on for a few seconds every time a
|
||||
button is pressed. While this setting is out of the hands of developers, apps
|
||||
can work to reduce the backlight on-time by minimizing the number of button
|
||||
presses required to operate them. For example, use an ``ActionBarLayer`` to
|
||||
execute common actions with one button press instead of a long scrolling
|
||||
``MenuLayer``.
|
||||
|
||||
While the ``Light`` API is available to manually turn the backlight on, it
|
||||
should not be used for more than very short periods, if at all. Apps that keep
|
||||
the backlight on all the time will not last more than a few hours. If the
|
||||
backlight must be kept on for an extended period, make sure to return to the
|
||||
automatic mode as soon as possible:
|
||||
|
||||
```c
|
||||
// Return to automatic backlight control
|
||||
light_enable(false);
|
||||
```
|
||||
|
||||
|
||||
## Vibration Motor Usage
|
||||
|
||||
As a physical converter of electrical to mechanical energy, the vibration motor
|
||||
also consumes a lot of power. Users can elect to use Quiet Time or turn off
|
||||
vibration for notifications to save power, but apps can also contribute to this
|
||||
effort. Try and keep the use of the ``Vibes`` API to a minimum and giving user
|
||||
the option to disable any vibrations the app emits. Another method to reduce
|
||||
vibrator power consumtion is to shorten the length of any custom sequences used.
|
||||
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about power consumtion on Pebble and how battery life can be
|
||||
extended through app design choices, watch the presentation below given at the
|
||||
2014 Developer Retreat.
|
||||
|
||||
[EMBED](//www.youtube.com/watch?v=TS0FPfgxAso)
|
39
devsite/source/_guides/best-practices/index.md
Normal file
39
devsite/source/_guides/best-practices/index.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
title: Best Practices
|
||||
description: |
|
||||
Information to help optimize apps and ensure a good user experience.
|
||||
guide_group: best-practices
|
||||
menu: false
|
||||
permalink: /guides/best-practices/
|
||||
generate_toc: false
|
||||
hide_comments: true
|
||||
---
|
||||
|
||||
In order to get the most out of the Pebble SDK, there are numerous opportunities
|
||||
for optimization that can allow apps to use power more efficiently, display
|
||||
correctly on all display shapes and sizes, and help keep large projects
|
||||
maintainable.
|
||||
|
||||
Information on these topics is contained in this collection of guides. Pebble
|
||||
recommends that developers try and incorporate as many of these practices into
|
||||
their apps as possible, to give themselves and users the best experience of
|
||||
their app.
|
||||
|
||||
|
||||
## Contents
|
||||
|
||||
{% include guides/contents-group.md group=page.group_data %}
|
|
@ -0,0 +1,274 @@
|
|||
---
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
title: Modular App Architecture
|
||||
description: |
|
||||
How to break up a complex app into smaller pieces for managablilty, modularity
|
||||
and reusability.
|
||||
guide_group: best-practices
|
||||
order: 3
|
||||
related_examples:
|
||||
- title: Modular App Example
|
||||
url: https://github.com/pebble-examples/modular-app-example/
|
||||
---
|
||||
|
||||
Most Pebble projects (such as a simple watchface) work fine as a single-file
|
||||
project. This means that all the code is located in one `.c` file. However, as
|
||||
the size of a single-file Pebble project increases, it can become harder to keep
|
||||
track of where all the different components are located, and to track down how
|
||||
they interact with each other. For example, a hypothetical app may have many
|
||||
``Window``s, perform communication over ``AppMessage`` with many types of data
|
||||
items, store and persist a large number of data items, or include components
|
||||
that may be valuable in other projects.
|
||||
|
||||
As a first example, the Pebble SDK is already composed of separate modules such
|
||||
as ``Window``, ``Layer``, ``AppMessage`` etc. The implementation of each is
|
||||
separate from the rest and the interface for developers to use in each module is
|
||||
clearly defined and will rarely change.
|
||||
|
||||
This guide aims to provide techniques that can be used to break up such an app.
|
||||
The advantages of a modular approach include:
|
||||
|
||||
* App ``Window``s can be kept separate and are easier to work on.
|
||||
|
||||
* A clearly defined interface between components ensures internal changes do not
|
||||
affect other modules.
|
||||
|
||||
* Modules can be re-used in other projects, or even made into sharable
|
||||
libraries.
|
||||
|
||||
* Inter-component variable dependencies do not occur, which can otherwise cause
|
||||
problems if their type or size changes.
|
||||
|
||||
* Sub-component complexity is hidden in each module.
|
||||
|
||||
* Simpler individual files promote maintainability.
|
||||
|
||||
* Modules can be more easily tested.
|
||||
|
||||
|
||||
## A Basic Project
|
||||
|
||||
A basic Pebble project starts life with the `new-project` command:
|
||||
|
||||
```bash
|
||||
$ pebble new-project modular-project
|
||||
```
|
||||
|
||||
This new project will contain the following default file structure. The
|
||||
`modular-project.c` file will contain the entire app, including `main()`,
|
||||
`init()` and `deinit()`, as well as a ``Window`` and a child ``TextLayer``.
|
||||
|
||||
```text
|
||||
modular-project/
|
||||
resources/
|
||||
src/
|
||||
modular-project.c
|
||||
package.json
|
||||
wscript
|
||||
```
|
||||
|
||||
For most projects, this structure is perfectly adequate. When the `.c` file
|
||||
grows to several hundred lines long and incorporates several sub-components with
|
||||
many points of interaction with each other through shared variables, the
|
||||
complexity reaches a point where some new techniques are needed.
|
||||
|
||||
|
||||
## Creating a Module
|
||||
|
||||
In this context, a 'module' can be thought of as a C header and source file
|
||||
'pair', a `.h` file describing the module's interface and a `.c` file containing
|
||||
the actual logic and code. The header contains standard statements to prevent
|
||||
redefinition from being `#include`d multiple times, as well as all the function
|
||||
prototypes the module makes available for other modules to use.
|
||||
|
||||
By making a sub-component of the app into a module, the need for messy global
|
||||
variables is removed and a clear interface between them is defined. The files
|
||||
themselves are located in a `modules` directory inside the project's main `src`
|
||||
directory, keeping them in a separate location to other components of the app.
|
||||
Thus the structure of the project with a `data` module added (and explained
|
||||
below) is now this:
|
||||
|
||||
```text
|
||||
modular-project/
|
||||
resources/
|
||||
src/
|
||||
modules/
|
||||
data.h
|
||||
data.c
|
||||
modular-project.c
|
||||
package.json
|
||||
wscript
|
||||
```
|
||||
|
||||
The example module's pair of files is shown below. It manages a dynamically
|
||||
allocated array of integers, and includes an interface to setting and getting
|
||||
values from the array. The array itself is private to the module thanks for the
|
||||
[`static`](https://en.wikipedia.org/wiki/Static_(keyword)) keyword. This
|
||||
technique allows other components of the app to call the 'getters' and 'setters'
|
||||
with the correct parameters as per the module's interface, without worrying
|
||||
about the implementation details.
|
||||
|
||||
`src/modules/data.h`
|
||||
|
||||
```c
|
||||
#pragma once // Prevent errors by being included multiple times
|
||||
|
||||
#include <pebble.h> // Pebble SDK symbols
|
||||
|
||||
void data_init(int array_length);
|
||||
|
||||
void data_deinit();
|
||||
|
||||
void data_set_array_value(int index, int new_value);
|
||||
|
||||
int data_get_array_value(int index);
|
||||
```
|
||||
|
||||
`src/modules/data.c`
|
||||
|
||||
```c
|
||||
#include "data.h"
|
||||
|
||||
static int* s_array;
|
||||
|
||||
void data_init(int array_length) {
|
||||
if(!s_array) {
|
||||
s_array = (int*)malloc(array_length * sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
void data_deinit() {
|
||||
if(s_array) {
|
||||
free(s_array);
|
||||
s_array = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void data_set_array_value(int index, int new_value) {
|
||||
s_array[index] = new_value;
|
||||
}
|
||||
|
||||
int data_get_array_value(int index) {
|
||||
return s_array[index];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Keep Multiple Windows Separate
|
||||
|
||||
The ``Window Stack`` lifecycle makes the task of keeping each ``Window``
|
||||
separate quite easy. Each one has a `.load` and `.unload` handler which should
|
||||
be used to create and destroy its UI components and other data.
|
||||
|
||||
The first step to modularizing the new app is to keep each ``Window`` in its own
|
||||
module. The first ``Window``'s code can be moved out of `src/modular-project.c`
|
||||
into a new module in `src/windows/` called 'main_window':
|
||||
|
||||
`src/windows/main_window.h`
|
||||
|
||||
```c
|
||||
#pragma once
|
||||
|
||||
#include <pebble.h>
|
||||
|
||||
void main_window_push();
|
||||
```
|
||||
|
||||
`src/windows/main_window.c`
|
||||
|
||||
```c
|
||||
#include "main_window.h"
|
||||
|
||||
static Window *s_window;
|
||||
|
||||
static void window_load(Window *window) {
|
||||
Layer *window_layer = window_get_root_layer(window);
|
||||
GRect bounds = layer_get_bounds(window_layer);
|
||||
}
|
||||
|
||||
static void window_unload(Window *window) {
|
||||
window_destroy(s_window);
|
||||
}
|
||||
|
||||
void main_window_push() {
|
||||
if(!s_window) {
|
||||
s_window = window_create();
|
||||
window_set_window_handlers(s_window, (WindowHandlers) {
|
||||
.load = window_load,
|
||||
.unload = window_unload,
|
||||
});
|
||||
}
|
||||
window_stack_push(s_window, true);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Keeping Main Clear
|
||||
|
||||
After moving the ``Window`` code out of the main `.c` file, it can be safely
|
||||
renamed `main.c` to reflect its contents. This allows the main `.c` file to show
|
||||
a high-level overview of the app as a whole. Simply `#include` the required
|
||||
modules and windows to initialize and deinitialize the rest of the app as
|
||||
necessary:
|
||||
|
||||
`src/main.c`
|
||||
|
||||
```c
|
||||
#include <pebble.h>
|
||||
|
||||
#include "modules/data.h"
|
||||
#include "windows/main_window.h"
|
||||
|
||||
static void init() {
|
||||
const int array_size = 16;
|
||||
data_init(array_size);
|
||||
|
||||
main_window_push();
|
||||
}
|
||||
|
||||
static void deinit() {
|
||||
data_deinit();
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
app_event_loop();
|
||||
deinit();
|
||||
}
|
||||
```
|
||||
|
||||
Thus the structure of the project is now:
|
||||
|
||||
```text
|
||||
modular-project/
|
||||
resources/
|
||||
src/
|
||||
modules/
|
||||
data.h
|
||||
data.c
|
||||
windows/
|
||||
main_window.h
|
||||
main_window.c
|
||||
main.c
|
||||
package.json
|
||||
wscript
|
||||
```
|
||||
|
||||
With this structured approach to organizing the different functional components
|
||||
of an app, the maintainability of the project will not suffer as it grows in
|
||||
size and complexity. A useful module can even be shared and reused as a library,
|
||||
which is preferrable to pasting chunks of code that may have other messy
|
||||
dependencies elsewhere in the project.
|
Loading…
Add table
Add a link
Reference in a new issue