Import the pebble dev site into devsite/

This commit is contained in:
Katharine Berry 2025-02-17 17:02:33 -08:00
parent 3b92768480
commit 527858cf4c
1359 changed files with 265431 additions and 0 deletions

View file

@ -0,0 +1,120 @@
---
# 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.
author: thomas
tags:
- Beautiful Code
---
_(2014 09: This article was updated to add links for SDK 2.0 users and add the resource identifier of the fonts as suggested by Timothy Gray in the comments.)_
Just like any modern platform, typography is a very important element of Pebble user interface. As Designers and Developers, you have the choice to use one of Pebble system fonts or to embed your own.
Pebble system fonts were chosen for their readability and excellent quality on Pebble black & white display, it's a good idea to know them well before choosing to embed your own.
## Using Pebble system fonts
To use one of the system font, you need to call `fonts_get_system_font()` and pass it the name of a system font. Those are defined in `pebble_fonts.h` in the `include` directory of your project.
This function returns a `GFont` object that you can use with `text_layer_set_text()` and `graphics_text_draw()`.
For example, in our Hello World example we used the _Roboto Condensed 21_ font:
```c
void handle_init(AppContextRef ctx) {
window_init(&window, "Window Name");
window_stack_push(&window, true /* Animated */);
text_layer_init(&hello_layer, GRect(0, 65, 144, 30));
text_layer_set_text_alignment(&hello_layer, GTextAlignmentCenter);
text_layer_set_text(&hello_layer, "Hello World!");
text_layer_set_font(&hello_layer, fonts_get_system_font(FONT_KEY_ROBOTO_CONDENSED_21));
layer_add_child(&window.layer, &hello_layer.layer);
}
```
> **Update for SDK 2 users**:
>
> Refer to the [second part of the watchface tutorial](/getting-started/watchface-tutorial/part2/) for updated sample code.
## An illustrated list of Pebble system fonts
{% alert notice %}
A more up-to-date list of fonts can be found by reading {% guide_link app-resources/system-fonts %}
in the developer guides.
{% endalert %}
To facilitate your choice, here is a series of screenshot of Pebble system fonts used to display digits and some text.
{% comment %}HTML below is acceptable according to Matthew{% endcomment %}
<table class="pebble-screenshots">
<tr>
<td>Gothic 14</td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_14_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_14_abc.png"></td>
</tr>
<tr><td>Gothic 14 Bold</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_14_bold_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_14_bold_abc.png"></td></tr>
<tr><td>Gothic 18</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_18_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_18_abc.png"></td></tr>
<tr><td>Gothic 18 Bold</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_18_bold_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_18_bold_abc.png"></td></tr>
<tr><td>Gothic 24</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_24_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_24_abc.png"></td></tr>
<tr><td>Gothic 24 Bold</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_24_bold_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_24_bold_abc.png"></td></tr>
<tr><td>Gothic 28</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_28_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_28_abc.png"></td></tr>
<tr><td>Gothic 28 Bold</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_28_bold_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/gothic_28_bold_abc.png"></td></tr>
<tr><td>Bitham 30 Black</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_30_black_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_30_black_abc.png"></td></tr>
<tr><td>Bitham 42 Bold</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_42_bold_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_42_bold_abc.png"></td></tr>
<tr><td>Bitham 42 Light</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_42_light_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_42_light_abc.png"></td></tr>
<tr><td>Bitham 34 Medium Numbers</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_34_medium_numbers_digits.png"></td>
<td></td></tr>
<tr><td>Bitham 42 Medium Numbers</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/bitham_42_medium_numbers_digits.png"></td>
<td></td></tr>
<tr><td>Roboto 21 Condensed</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/roboto_21_condensed_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/roboto_21_condensed_abc.png"></td></tr>
<tr><td>Roboto 49 Bold Subset</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/roboto_49_bold_subset_digits.png"></td>
<td></td></tr>
<tr><td>Droid 28 Bold</td><td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/droid_28_bold_digits.png"></td>
<td><img src="{{ site.asset_path }}/images/blog/pebble-fonts/droid_28_bold_abc.png"></td></tr>
</table>
## A word of caution
To save space on Pebble's memory, some of the system fonts only contain a subset of the default character set:
* _Roboto 49 Bold Subset_ only contains digits and a colon;
* _Bitham 34/42 Medium Numbers_ only contain digits and a colon;
* _Bitham 18/34 Light Subset_ only contains a few characters and you should not use them (this why they are not included in the screenshots).
## Browse the fonts on your watch
To help you decide which font is best, you will probably want to test those fonts directly on a watch.
We have added a new demo application called [`app_font_browser`]({{site.links.examples_org}}/app-font-browser) to the SDK to help you do that. This application uses a ``MenuLayer`` to navigate through the different options and when you have chosen a font, you can use the _Select_ button to cycle through different messages.
![app_font_browser](/images/blog/app_font_browser.png)
You can very easily customize the list of messages for your needs and as an exercise for the reader, you can adapt the app to show custom fonts as well!
This example is available in the `Examples/watchapps/app_font_browser` of the SDK and on [Github]({{site.links.examples_org}}/app-font-browser).

View file

@ -0,0 +1,196 @@
---
# 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.
author: team pebble
tags:
- Beautiful Code
---
PebbleKit JavaScript is one of the most popular additions in Pebble SDK 2.0. We are already seeing lots of amazing apps being built that take advantage of http requests, configuration UI, GPS and local storage on the phone.
Here are some Tips & Tricks to get the most out of JavaScript in your Pebble apps.
### Lint it!
Use [JSLint](http://jslint.com/) ([GitHub](https://github.com/douglascrockford/JSLint)) to check that your Javascript is valid. JSLint with default settings has been successful in catching most of the JS errors that have been reported to us.
If you lint it, there's a much better chance that your app will run smoothly on both the Android and iOS platforms.
### Make the best use of your data storage
There are two types of storage that are used with the Pebble, persistent storage and local storage. [Persistent storage](``Storage``) is available through a C API and saves data on Pebble. [Local storage](/guides/communication/using-pebblekit-js) is a JavaScript API and saves data on the phone. These two long-term storage systems are not automatically connected and you will need to use the [AppMessage](``AppMessage``) or [AppSync](``AppSync``) API's to transfer data from one to the other.
Persistent storage is a key-value storage where the values are limited to 256 bytes and the total space used cannot exceed 4 kb. Persistent storage is not erased when you upgrade the app - but it is erased if you uninstall the app. (Please not that this will be true in Pebble SDK 2.0-BETA4 but was not before).
Local storage exists on your mobile phone and will persist between app upgrades. It is important that you periodically review the data that your app is storing locally and remove any data that is unneeded or no longer relevant. This can be especially important when your internal app data structures change in an update to your app, as your new code will be expecting data formatted in a deprecated form.
We strongly recommend that you version your data (adding a key `version` for example) so that a new version of your app can upgrade the format of your data and erase obsolete data..
### Check for ACK/NACK responses
It is imperative that you check for ACK/NACK responses to your last message between your Pebble and phone before initiating another message. Only one message can be sent at any one time reliably and if you try to send additional messages before ACK/NACK responses are received, you will start seeing failed messages and will have to resend them.
[Pebble-faces](https://github.com/pebble-examples/pebble-faces/blob/master/src/js/pebble-js-app.js) is a good example that includes a `transferImageBytes()` function that sends an image to Pebble, waiting for an ACK after each packet.
### Familiarize yourself with the Geolocation API
The W3C has [extensive documentation](http://www.w3.org/TR/geolocation-API/) on how to use the API, as well as best practices.
Please be aware that support of this API is dependent on the underlying OS and results may vary wildly depending on the type of phone, OS version and environment of the user. There are situations where you may not get a response right away, you may get cached data, or you may never get a callback from your phone.
We recommend that you:
* Always pass an error handler to `getCurrentPosition()` and `watchPosition()` - and test the error scenarios!
* Set up a timeout (with `setTimeout()`) when you make a geolocation request. Set it to 5 or 10 seconds and let the user know that something is wrong if the timeout fires before any of the callbacks.
* Take advantage of the different options you can pass to customize the precision and freshness of data.
* Test what happens if you disable location services on Pebble (on iOS, remove or refuse the authorization to get location, or turn off the iPhone's radio and Wifi ; on Android disable all the location services).
* On your watch app, communicate to the user when the system is looking for geolocation information, and gracefully handle the situation when Geolocation information is not available.
### Double check your strings for malformed data
Not all valid strings are valid in the context in which they're used. For instance, spaces in an URL are a bad idea and will throw exceptions. (It may also crash the iOS app in some earlier version of the SDK - Dont do it!) JSLint will not catch errors in quoted strings.
### Use the `ready` event
The `ready` event is fired when the JS VM is loaded and ready to run. Do not start network request, try to read localstorage, communication with Pebble or perform similar operations before the `ready` event is fired!
In practice this means that instead of writing:
console.log("My app has started - Doing stuff...");
Pebble.showSimpleNotificationOnPebble("BadApp", "I am running!");
You should do:
Pebble.addEventListener("ready", function() {
console.log("My app has started - Doing stuff...");
Pebble.showSimpleNotificationOnPebble("BadApp", "I am running!");
});
### Do not use recurring timers such as setInterval()
Your Pebble JavaScript can be killed at any time and you should not rely on it to provide long running services. In the future we may also limit the running time of your app and/or limit the use of intervals.
Follow these guidelines to get the most out of long running apps:
- Design your app so that it can be killed at any time. Save any important configuration or state in local storage when you get it - and reload it in the `ready` event.
- If you need some function to run at regular intervals, set a timer on Pebble and send a message to the JavaScript. If the JavaScript is dead, the mobile app will re-initialize it and run the message handler.
- Do not use `setInterval()`. And of course, do not use `setTimeout()` to simulate intervals.
### Identify Users using the PebbleKit JS account token
PebbleKit JavaScript provides a unique account token that is associated with the Pebble account of the current user. The account token is a string that is guaranteed to be identical across devices that belong to the user, but is unique to your app and cannot be used to track users across applications.
If the user is not logged in, the function will return an empty string (""). The account token can be accessed via the `Pebble.getAccountToken()` function call.
Please note that PebbleKit JavaScript does not have access to the serial number of the Pebble connected.
### Determine a User's timezone using a simple JS script
Pebble does not support timezones yet so when Pebble syncs the current time with your phone, it retrieves your local time and stores the local time as a GMT time (ex. 9AM PST -> 9AM GMT), regardless of where you are located in the world.
If you need to get the current timezone in your app, you can use this simple JavaScript code snippet:
function sendTimezoneToWatch() {
// Get the number of seconds to add to convert localtime to utc
var offsetMinutes = new Date().getTimezoneOffset() * 60;
// Send it to the watch
Pebble.sendAppMessage({ timezoneOffset: offsetMinutes })
}
You will need to assign a value to the `timezoneOffset` appKey in your `appinfo.json`:
{
...
"appKeys": {
"timezoneOffset": 42,
},
...
}
And this is how you would use it on Pebble:
#define KEY_TIMEZONE_OFFSET 42
static void appmsg_in_received(DictionaryIterator *received, void *context) {
Tuple *timezone_offset_tuple = dict_find(received, KEY_TIMEZONE_OFFSET);
if (timezone_offset_tuple) {
int32_t timezone_offset = timezone_offset_tuple->value->int32;
// Calculate UTC time
time_t local, utc;
time(&local);
utc = local + timezone_offset;
}
}
### Use the source!
We have a growing number of JS examples in the SDK and in our [`pebble-hacks`](https://github.com/pebble-hacks) GitHub account. Check them out!
You can also [search for `Pebble.addEventListener` on GitHub](https://github.com/search?l=&q=Pebble.addEventListener+in%3Afile&ref=advsearch&type=Code) to find more open-source examples.
## Best Practices for the JS Configuration UI
The new configuration UI allows you to display a webview on the phone to configure your Pebble watchface or app. Here are some specific recommendations to development of that UI.
If you have not read it yet, you might want to take a look at our [Using PebbleKit JS for Configuration](/blog/2013/11/21/Using-PebbleKit-JS-Configuration/) blog post.
### Immediately start loading your Web UI
To avoid confusion, make sure UI elements are displayed immediately after the user clicks on settings. This means you should not do any http request or complex processing before calling `Pebble.openURL()` when you get the `showConfiguration` event:
Pebble.addEventListener("showConfiguration", function() {
Pebble.openURL('http://www.example.com/mypebbleurl');
});
If the UI rendering is delayed, the user will get no feedbacks for their action (a press on the configure icon) and this would result in a poor user experience.
### Apply mobile & web best practices
There is a lot of information on the web on how to build webpages that display nicely on mobile devices. iOS and Android both use WebKit based browsers which makes things easier, but it is safe to assume that you will never get the exact same look - and you will always need to test on both platforms. Even if you do not own both devices, you can use the built-in browser in the iOS or Android emulators to test your configuration webpage.
Also consider using framework such as [JQuery Mobile](http://jquerymobile.com/) or [Sencha Touch](http://www.sencha.com/products/touch/) to make your life easier. They provide easy to use components and page templates. If you do not want to write any html, [setpebble.com](http://www.setpebble.com) is another great option that we have [discussed on the blog](/blog/2013/12/02/SetPebble-By-Matt-Clark/) previously.
### Be prepared for the network to be unreachable during a UI configuration attempt
UI configuration HTML is not stored locally on the phone and must be accessed from a hosted server. This introduces situations where a user's phone may not be connected to the network successfully, or your server may be unreachable, in which case the UI configuration screen will not load.
In this scenario, a user will likely cancel the configuration load, which means a blank string is returned to your application `webviewclosed` event. Your watchapp should be designed to handle this such that the watchapp does not erase all settings or crash.
### Display existing settings
When displaying your configuration UI, remember to pass existing settings to the webview and use them to pre-populate the UI!
### Name your configuration submission button "Save"
If you want to ensure that the settings a user has entered are saved correctly, make sure you call your button _Save_ so users understand that it is required to save data. If your configuration page is long, consider putting the _Save_ button at both the top and bottom of the page.
If the user clicks the Exit button (X), a blank string will be passed back to your application. You should be prepared to handle a blank string should a user decide to cancel the configuration process.
### Prompt users to complete app configuration in your Pebble app
Users may not be aware that they need to complete a configuration for your watchapp. It is best to advise users in the watchface or watchapp to go to their phone and configure your app before proceeding.
Use persistent storage or local storage to save a token indicating the user has completed configuration, and consider putting a version string in, to allow you to track which version of your configuration webpage the user has accessed (to assist future upgrades of your app).
## Questions? More tips?
Have more questions? Or want to share more tips? Please post them in the comments below!

View file

@ -0,0 +1,129 @@
---
# 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: Using JSHint for Pebble Development
author: thomas
tags:
- Beautiful Code
---
In our post on [JavaScript Tips and Tricks](/blog/2013/12/20/Pebble-Javascript-Tips-and-Tricks/) we strongly recommended the use of JSLint or [JSHint](http://www.jshint.com/) to help you detect JavaScript problems at compile time.
We believe JSHint can really increase the quality of your code and we will probably enforce JSHint correct-ness for all the apps on the appstore very soon. In this post we show you how to setup jshint and integrate it in your Pebble development process.
## Installing JSHint
JSHint is a modern alternative to JSLint. It is more customizable and you can install it and run it on your machine. This allows you to run JSHint every time you build your Pebble application and fix errors right away. This will save you a lot of install/crash/fix cycles!
JSHint is a [npm package](https://npmjs.org/package/jshint). NPM is included in all recent NodeJS installations. On Mac and Windows you can [download an installer package](http://nodejs.org/download/). On most Linux distribution, you will install nodejs with the standard package manage (`apt-get install nodejs`).
To install the JSHint package, run this command:
sudo npm install -g jshint
The `-g` flag tells npm to install this package globally, so that the `jshint` command line tool is always available.
## Configuring jshint
JSHint offers a large selection of options to customize its output and disable some rules if you absolutely have to. We have prepared a default configuration file that we recommend for Pebble development. The first half should not be changed as they are the rules that we may enforce on the appstore in the future. The second half is our recommended best practices but you can change them to suit your coding habits.
Save this configuration in a `pebble-jshintrc` file in your Pebble project.
/*
* Example jshint configuration file for Pebble development.
*
* Check out the full documentation at http://www.jshint.com/docs/options/
*/
{
// Declares the existence of a global 'Pebble' object
"globals": { "Pebble" : true },
// And standard objects (XMLHttpRequest and console)
"browser": true,
"devel": true,
// Do not mess with standard JavaScript objects (Array, Date, etc)
"freeze": true,
// Do not use eval! Keep this warning turned on (ie: false)
"evil": false,
/*
* The options below are more style/developer dependent.
* Customize to your liking.
*/
// All variables should be in camelcase
"camelcase": true,
// Do not allow blocks without { }
"curly": true,
// Prohibits the use of immediate function invocations without wrapping them in parentheses
"immed": true,
// Enforce indentation
"indent": true,
// Do not use a variable before it's defined
"latedef": "nofunc",
// Spot undefined variables
"undef": "true",
// Spot unused variables
"unused": "true"
}
## Automatically running jshint
To automatically run jshint every time you compile your application, you will need to edit your `wscript` build file. This is the modified `wscript` file:
# Use the python sh module to run the jshint command
from sh import jshint
top = '.'
out = 'build'
def options(ctx):
ctx.load('pebble_sdk')
def configure(ctx):
ctx.load('pebble_sdk')
# Always pass the '--config pebble-jshintrc' option to jshint
jshint.bake(['--config', 'pebble-jshintrc'])
def build(ctx):
ctx.load('pebble_sdk')
# Run jshint before compiling the app.
jshint("src/js/pebble-js-app.js")
ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
target='pebble-app.elf')
ctx.pbl_bundle(elf='pebble-app.elf',
js=ctx.path.ant_glob('src/js/**/*.js'))
Now simply run `pebble build` and if there is anything wrong in your JS file, the build will stop and the errors will be displayed on the console. You won't believe how much time you will save!
## CloudPebble users
CloudPebble users are encouraged to download and install JSHint on their machine. You can use the [web interface](http://www.jshint.com/) and set most of the recommended options but you will never get 100% correctness because there is no way to declare an extra global variable (the `Pebble` object).
Good news is [CloudPebble is opensource](https://github.com/CloudPebble/CloudPebble/)! We would be happy to offer a brand new [Pebble Steel](http://www.getpebble.com/steel) to the developer who will submit and get through Katherine's approval a pull request to add jshint support in CloudPebble ;)

View file

@ -0,0 +1,104 @@
---
# 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: FreeRTOS™ Code Revisions from Pebble
author: brad
tags:
- Down the Rabbit Hole
---
Today Pebble is releasing its recent modifications to the FreeRTOS project.
Pebble has made a few minor changes to FreeRTOS to enable its new sandboxed
application environment for PebbleOS 2.0 as well as to make Pebble easier to
monitor and debug.
The changes are available
[as a tarball](http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/dev-portal/FreeRTOS-8.0.0-Pebble.tar.gz)
.
Read on to learn more about the changes and why they were made.
The new PebbleOS 2.0 application sandbox environment allows running third party
native code (Pebble Apps) in a safe and secure manner. In the sandbox, any
application errors should not negatively impact the host operating system.
Because of the hardware restrictions of a Pebble watch, the implementation is
achieved in a basic manner. Pebble Apps are run in their own FreeRTOS task that
is unprivileged. This means that the instructions the app is authorized to run
are restricted. For example, its not allowed to change the `CONTROL` register
(which changes whether the app is privileged or not) or change interrupt
settings. Furthermore, Pebble restricts the memory the app can access to a small
fixed region. This is done using the MPU (Memory Protection Unit) hardware
available in the Pebble microcontroller. Accesses outside of this region will
cause the application to be stopped. This way Pebble can make sure the
application is only interacting with the kernel in ways that will not interfere
with other features and functions.
The FreeRTOS implementation includes a port that uses the MPU (ARM_CM3_MPU)
which is incompatible with the project goals. This port appears meant for safety
critical environments where tasks shouldnt be allowed to accidentally interact
with each other. However, there doesnt seem to be any protection from a
malicious task. For example, raising a task's privilege level is as easy as
triggering a software interrupt, which unprivileged tasks are free to use. “MPU
wrapper” functions are provided to allow any task to call a FreeRTOS API
function from any privilege level and operate as a privileged function, ignoring
any MPU settings. The sandbox is intended to restrict how the application
FreeRTOS task is allowed to interact with the kernel, so modifications were
necessary.
To solve this, Pebble has created its own port named ARM_CM3_PEBBLE. This port
is based on the ARM_CM3_MPU port, but is modified to use the MPU in a different
way. No wrappers are provided (you need to be privileged to directly call
FreeRTOS functions) and the `portSVC_RAISE_PRIVILEGE` software interrupt is
removed.
To permit the app to interact with the system in a safe manner, Pebble added a
new software interrupt, called `portSVC_SYSCALL`. Unprivileged code can use it
to jump into the operating system in a privileged state but only using a system-
provided jump-table. The jump-table contains the address to landing functions
(we refer to them as syscalls) that are individually responsible for checking
that the operation is permitted and the parameters are safe and valid.
Pebble has also made some minor changes to how tasks are created and destroyed.
In order for the application to keep working inside its sandbox, it needs access
to certain resources like its own stack memory. FreeRTOS allows a programmer to
specify a buffer to be used as the stack, so a buffer that's allocated inside
the app sandbox memory region is used. However, FreeRTOS has a bug where it
tries to deallocate the stack memory when a task is destroyed, regardless of
where that memory came from. Pebble changed this code to only free the stack
when it was not provided by the system.
Pebble also added the ability for the system to designate its own buffer for the
`_reent` struct. This struct is a feature of the c library we use - newlib -
that contains buffers that are used by various libc functions. For example,
`localtime` returns a pointer to a `struct tm` structure. This struct is
actually stored in the `_reent` struct. FreeRTOS has `_reent` support so which
`_reent` struct is currently being used is swapped around per task, so each task
has their own `_reent` struct to play with and you dont have issues if two
threads call `localtime` at the same time. To ensure the `_reent` struct for the
application task is available within the sandboxed memory region, Pebble pre-
allocated it and passed it through to `xTaskCreate`.
Finally, Pebble has added some functionality to inspect the saved registers of
tasks that arent currently running. This allows Pebble to collect additional
diagnostics if the watch exhibits any errors and to provide basic crash
reporting for apps. For example, App developers will get a dump of the apps PC
(Program Counter) and LR (Link Register) registers at the time of the crash.
Pebble is incredibly thankful for all the work that the FreeRTOS community has
put into the code. It has been a huge asset when building PebbleOS. We'll be
releasing additional updates as we continue to modify FreeRTOS in the future. If
you have any questions, dont hesitate to [contact us](/contact).

View file

@ -0,0 +1,333 @@
---
# 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 SDK Tutorial (Part 1)
author: Chris Lewis
excerpt: |
This is the first in a series of articles that will hopefully help newcomers
and seasoned programmers alike begin to flex their creative muscles and make
their own Pebble watchapps. If you prefer a more visual approach, Thomas has
made videos you can watch to help you get started.
---
##Your First Watchapp
###Introduction
This is the first in a series of articles that will hopefully help newcomers and
seasoned programmers alike begin to flex their creative muscles and make their
own Pebble watchapps. If you prefer a more visual approach, Thomas has made
videos you can watch
[here](https://www.youtube.com/channel/UCFnawAsyEiux7oPWvGPJCJQ) to help you get
started.
Firstly, make sure you are familiar with the following basic C language
concepts. Pebble has [Developer Guides](/guides/) that may help:
- Variables and variable scope (local or global?)
- Functions (definitions and calls)
- Structures
- Pointers
- Program flow (if, else etc.)
- Preprocessor statements (`#define`, `#include` etc.)
Up to speed? Good! Let's create our first watchapp.
###Development Environment
The easiest way to get started is to use [CloudPebble]({{site.links.cloudpebble}})
, a free online IDE for Pebble. This approach requires no installations to start
making watchapps. Advanced users can install the SDK on Linux, OS X or Windows
(via VM such as [VirtualBox](https://www.virtualbox.org/)) for a more hands-on
approach. Instructions for installing the native SDK can be found on the
[Pebble Developer site](/sdk/install/linux/)
. For this tutorial, we will be using CloudPebble.
###First Steps
To get started, log into [CloudPebble]({{site.links.cloudpebble}}) and choose
'Create Project'.
1. Enter a suitable name for the project such as 'My First App'.
2. Set the project type to 'Pebble C SDK'.
3. Set the template as 'Empty project'.
4. Confirm with 'Create'.
To start entering code, choose 'New C file' on the left hand pane. C language
source files typically have the extension '.c'. A suggested standard name is
`main.c`, although this can be anything you like.
###Setting Up the Basics
The power behind the C language comes from its ability to be adapted for many
different devices and platforms, such as the Pebble watch, through the use of
SDKs such as this one. Therefore, to be able to use all the features the Pebble
SDK has to offer us, we must include it at the start of the file by using the
`#include` preprocessor statement (meaning it is processed before the rest of
the code):
```c
#include <pebble.h>
```
All C applications begin with a call to a function called `main()` by the host
operating system, also known as the entry point of the program. This must
contain ``app_event_loop()``, and by convention also contains two other
functions to manage the start and end of the watchapp's life:
- `init()` - Used to create (or initialize) our app.
- ``app_event_loop()`` - Handles all events that happen from the start of the
app to the end of its life.
- `deinit()` - Used to clean up after ourselves and make sure any memory we use
is free for apps that are run after us.
Following these rules, our first function, `main()` looks like this:
```c
int main(void)
{
init();
app_event_loop();
deinit();
}
```
All functions we call must be defined before they are used so that they are
known to the compiler when the first call is encountered. To this end we will
define our `init()` and `deinit()` functions before they are called in `main()`
by placing them after the `#include` preprocessor statement and before the
definition of `main()` itself. The resulting code file now looks like this:
```c
#include <pebble.h>
void init()
{
//Create app elements here
}
void deinit()
{
//Destroy app elements here
}
int main(void)
{
init();
app_event_loop();
deinit();
}
```
###Using the SDK
With the boilerplate app structure done, let's begin using the Pebble C SDK to
create our first watchapp. The first element we will use is the ``Window``. We
will declare our first instance of a ``Window`` outside the scope of any
function in what is known as a 'global variable', before it is first used. The
reason for this is for us to be able to use this reference from any location
within the program. By convention, a global variable's name is prefixed with
`g_` to make it easily identifiable as such. An ideal place for these
declarations is below the preprocessor statements in the source file.
Declare a pointer to a ``Window`` structure to be created later like so:
```c
Window *g_window;
```
At the moment, this pointer does not point anywhere and is unusable. The next
step is to use the ``window_create()`` function to construct a ``Window``
element for the pointer to point to. Once this is done, we also register two
references to the `window_load()` and `window_unload()` functions (known as
handlers) which will manage the creation and destruction of the elements within
that window. This is shown below:
```c
void init()
{
//Create the app elements here!
g_window = window_create();
window_set_window_handlers(g_window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
}
```
The next logical step is to define what we mean by `window_load()` and
`window_unload()`. In a similar fashion to `init()` and `deinit()`, these must
be defined before they are first used; above `init()`, but below the definition
of `g_window` at the top of the file. Think of them as the `init()` and
`deinit()` equivalent functions for the ``Window``:
```c
void window_load(Window *window)
{
//We will add the creation of the Window's elements here soon!
}
void window_unload(Window *window)
{
//We will safely destroy the Window's elements here!
}
```
This modular approach to app creation allows a developer to create apps with all
relevant sub-elements managed within the life of that particular ``Window``.
This process is shown visually in the diagram below:
![Lifecycle](/images/blog/tut_1_lifecycle.png "Lifecycle")
As a responsible app developer, it is a good practice to free up any memory we
use to the system when our app is closed. Pebble SDK elements use functions
suffixed with `_destroy` for this purpose, which can be done for our ``Window``
element in `deinit()`:
```c
void deinit()
{
//We will safely destroy the Window's elements here!
window_destroy(g_window);
}
```
The final step in this process is to make the app actually appear when it is
chosen from the Pebble menu. To do this we push our window to the top of the
window stack using ``window_stack_push()``, placing it in the foreground in
front of the user. This is done after the ``Window`` is created, and so we will
place this call at the end of `init()`:
```c
window_stack_push(g_window, true);
```
The second argument denotes whether we want the push to be animated. This looks
cool, so we use `true`! The app will now launch, but will be completely blank.
Let's rectify that.
###Displaying Content
So far we have created and pushed an empty ``Window`` element. So far so good.
Now let's add our first sub-element to make it show some text. Introducing the
``TextLayer``! This is an element used to show any standard string of characters
in a pre-defined area, called a 'frame'. As with any element in the SDK, we
begin with a global pointer to a structure-to-be of that type:
```c
TextLayer *g_text_layer;
```
The rest of the process of using the ``TextLayer`` is very similar to that of
the ``Window``. This is by design, and means it is easy to tell which functions
to use as they are named in a 'plain English' style. Creating the ``TextLayer``
will be done within the `window_load()` function, as it will be shown within the
parent ``Window`` (here called `g_window`).
The functions we call to perform this setup are almost self-explanatory, but I
will go through them after the following segment. Can you spot the pattern in
the SDK function names?
```c
g_text_layer = text_layer_create(GRect(0, 0, 144, 168));
text_layer_set_background_color(g_text_layer, GColorClear);
text_layer_set_text_color(g_text_layer, GColorBlack);
layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer));
```
Now the explanation:
1. ``text_layer_create()`` - This creates the ``TextLayer`` and sets its frame
to that in the ``GRect`` supplied as its only argument to x = 0, y = 0, width
= 144 pixels and height = 168 pixels (this is the size of the entire screen).
Just like ``window_create()``, this function returns a pointer to the newly
created ``TextLayer``
2. ``text_layer_set_background_color()`` - This also does what it says: sets the
``TextLayer``'s background color to ``GColorClear``, which is transparent.
3. ``text_layer_set_text_color()`` - Similar to the last function, but sets the
text color to ``GColorBlack``.
4. ``layer_add_child()`` - This is used to add the ``TextLayer``'s "root"
``Layer`` (which is the part drawn to the screen) as a 'child' of the
``Window``'s root layer. Simply put, all child ``Layer``s are drawn in front
of their 'parents' and allows us to control layering of ``Layer``s and in
which order they are drawn.
As should always be the case, we must add the required destruction function
calls to free up the memory we used in creating the ``TextLayer``. This is done
in the parent ``Window``'s `window_unload()` function, which now looks like
this:
```c
void window_unload(Window *window)
{
//We will safely destroy the Window's elements here!
text_layer_destroy(g_text_layer);
}
```
Now for what we have been working towards all this time - making the app show
any text we want! After creating the ``TextLayer``, add a call to
``text_layer_set_text()`` to set the text it should display, such as the example
below:
```c
text_layer_set_text(g_text_layer, "Anything you want, as long as it is in quotes!");
```
Now you should be ready to see the fruits of your labor on your wrist! To do
this we must compile our C source code into a `.pbw` package file that the
Pebble app will install for us.
###Compilation and Installation
To do this, make sure you save your C file. Then go to the 'Compilation' screen
in the left pane and click 'Run build'. After the compiler returns 'Succeeded',
you can use either of the following options to install the app on your Pebble:
- Ensure you have enabled the
[Pebble Developer Connection](/guides/tools-and-resources/developer-connection/)
and enter the IP address shown into the 'Phone IP' box. Click 'Install and
Run' to install your app.
- Use the 'Get PBW' button in your phone's browser to install via the Pebble app.
If your code does not compile, compare it to the
[sample code](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) to see
where you might have made an error. Once this is successful, you should see
something similar to the screenshot below:
![Screenshot](/images/blog/tut_1_preview.png "Screenshot")
###Conclusions
There you have it, a *very* simple watchapp to show some text of your choosing.
This was done by:
1. Setting up boilerplate app structure.
2. Creating a ``Window`` with a child layer, in this case the ``TextLayer``.
3. Creating a ``TextLayer`` to show text on the screen.
By adding more types of layers such as ``BitmapLayer`` and `InverterLayer` we
create much more sophisticated apps. By extension with ``AppMessage``s and
``Clicks`` we can begin to respond to user data and input. All this and more to
come in future instalments!
###Source Code
The full source code file we have built up over the course of this article can
be found [here](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) and
directly imported into CloudPebble
[as a new project](https://cloudpebble.net/ide/gist/a911f0b053260f209390). If
your code doesn't compile, have a look at this and see where you may have gone
wrong.
Best of luck, and let me know what you come up with! Send any feedback or
questions to [@PebbleDev](http://twitter.com/PebbleDev) or
[@Chris_DL](http://twitter.com/Chris_DL).

View file

@ -0,0 +1,237 @@
---
# 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 SDK Tutorial (Part 2)
author: Chris Lewis
excerpt: |
This is the second in a series of articles aimed at helping developers new and
experienced begin creating their own watchapps. In this section we will be
using the TickTimerService to display the current time, which is the basis of
all great watchfaces. After this, we will use GBitmaps and BitmapLayers to
improve the aesthetics of our watchface.
---
##Your First Watchface
###Previous Tutorial Parts
[Pebble SDK Tutorial (Part 1): Your First Watchapp](/blog/2014/06/10/Pebble-SDK-Tutorial-1/)
###Introduction
This is the second in a series of articles aimed at helping developers new and
experienced begin creating their own watchapps. In this section we will be using
the ``TickTimerService`` to display the current time, which is the basis of all
great watchfaces. After this, we will use ``GBitmap``s and ``BitmapLayer``s to
improve the aesthetics of our watchface. By the end of this section, our app
will look like this:
![final](/images/blog/tut_2_frame_added.png "final")
###Getting Started
To begin creating this watchface, we will be using the
[source code](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) from the
last post as a starting point, which you can import into
[CloudPebble]({{site.links.cloudpebble}}) as a new project
[here](https://cloudpebble.net/ide/gist/a911f0b053260f209390).
As you may have noticed, the current watchapp is started from the Pebble main
menu, and not on the watchface carousel like other watchfaces. To change this,
go to 'Settings' in your CloudPebble project and change the 'App Kind' to
'Watchface'. This will cause the app to behave in the same way as any other
watchface. If you were using the native SDK, this change would be done in the
`appinfo.json` file.
Once this is done, we will modify the main C file to prepare it for showing the
time. Remove the call to ``text_layer_set_text()`` to prevent the watchface
showing anything irrelevant before the time is shown. For reference,
`window_load()` should now look like this:
```c
void window_load(Window *window)
{
//We will add the creation of the Window's elements here soon!
g_text_layer = text_layer_create(GRect(0, 0, 144, 168));
text_layer_set_background_color(g_text_layer, GColorClear);
text_layer_set_text_color(g_text_layer, GColorBlack);
layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer));
}
```
###Telling the Time
Now we can use the ``TextLayer`` we created earlier to display the current time,
which is provided to us once we register a ``TickHandler`` with the system. The
first step to do this is to create an empty function in the format dictated by
the ``TickHandler`` documentation page. This is best placed before it will be
used, which is above `init()`:
```c
void tick_handler(struct tm *tick_time, TimeUnits units_changed)
{
}
```
This function is called by the system at a fixed tick rate depending on the
``TimeUnits`` value supplied when we register the handler. Let's do this now in
`init()` before ``window_stack_push()``, using the ``MINUTE_UNIT`` value to get
an update every minute change:
```c
tick_timer_service_subscribe(MINUTE_UNIT, (TickHandler)tick_handler);
```
Now that we have subscribed to the ``TickTimerService``, we can add code to the
`tick_handler()` function to use the time information provided in the
`tick_time` parameter to update the ``TextLayer``. The data stored within this
structure follows the
[ctime struct tm format](http://www.cplusplus.com/reference/ctime/tm/). This
means that we can access the current hour by using `tick_time->tm_hour` and the
current minute by using `tick_time->tm_min`, for example. Instead, we will use a
function called `strftime()` that uses the `tick_time` parameter to write a
time-formatted string into a buffer we provide, called `buffer`. Therefore the
new tick handler will look like this:
```c
void tick_handler(struct tm *tick_time, TimeUnits units_changed)
{
//Allocate long-lived storage (required by TextLayer)
static char buffer[] = "00:00";
//Write the time to the buffer in a safe manner
strftime(buffer, sizeof("00:00"), "%H:%M", tick_time);
//Set the TextLayer to display the buffer
text_layer_set_text(g_text_layer, buffer);
}
```
Now every time a minute passes `tick_handler()` will create a new string in the
buffer and display it in the ``TextLayer``. This is the basis of every
watchface. However, the text contained in the ``TextLayer`` is difficult to read
because the default system font is very samll, so we will change some of the
layout properties to better suit its purpose. Modify `window_load()` to add the
relevant ``TextLayer`` functions like so:
```c
void window_load(Window *window)
{
//Create the TextLayer
g_text_layer = text_layer_create(GRect(0, 59, 144, 50));
text_layer_set_background_color(g_text_layer, GColorClear);
text_layer_set_text_color(g_text_layer, GColorBlack);
//Improve the layout to be more like a watchface
text_layer_set_font(g_text_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
text_layer_set_text_alignment(g_text_layer, GTextAlignmentCenter);
layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer));
}
```
Now the watchface should look more like a classic watchface with a larger font
and centered text:
![largetext](/images/blog/tut_2_largetext.png "large text")
###Adding Bitmaps
The next logical way to improve the watchface is to add some complementary
images. These take the form of ``GBitmap``s, and are referenced by a
`RESOURCE_ID` specified in CloudPebble Settings or `appinfo.json` when working
with the native SDK. The first bitmap we wil add will be a smart border for the
``TextLayer``, shown for you to use below:
![frame](/images/blog/tut_2_frame.png "frame")
To add this image to our project in CloudPebble, click "Add New" next to
Resources, navigate to your stored copy of the image above and give it an ID
such as "FRAME", then click Save.
Once this is done, we can add it to the watchface. The first step is to declare
two new global items: A ``GBitmap`` to hold the loaded image and a
``BitmapLayer`` to show it. Add these to the top of the file, alongside the
existing global variables:
```c
GBitmap *g_frame_bitmap;
BitmapLayer *g_frame_layer;
```
The next step is to allocate the ``GBitmap``, show it using the ``BitmapLayer``
and add it as a child of the main ``Window``. ``Layer``s are drawn in the order
they are added to their parent, so be sure to add the ``BitmapLayer`` **before**
the ``TextLayer``. This will ensure that the time is drawn on top of the image,
and not the other way around:
```c
//Create and add the image
g_frame_bitmap = gbitmap_create_with_resource(RESOURCE_ID_FRAME);
g_frame_layer = bitmap_layer_create(GRect(7, 56, 129, 60));
bitmap_layer_set_bitmap(g_frame_layer, g_frame_bitmap);
layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(g_frame_layer));
```
Once again, remember to add calls to `_destroy()` for each element to free
memory in `window_unload()`:
```c
void window_unload(Window *window)
{
//We will safely destroy the Window's elements here!
text_layer_destroy(g_text_layer);
gbitmap_destroy(g_frame_bitmap);
bitmap_layer_destroy(g_frame_layer);
}
```
With these steps completed, a re-compile of your project should yield something
similar to that shown below:
![frameadded](/images/blog/tut_2_frame_added.png "frame added")
The ``TextLayer`` is now surrounded by a crisp frame border. It's not amazing,
but it's the start on a journey to a great watchface!
###Conclusion
There you have it: turning your simple watchapp into a basic watchface. In
summary:
1. Modify the App Kind to be a watchface instead of a watchapp.
2. Add a subscription to the ``TickTimerService`` to get the current time.
3. Modify the ``TextLayer`` layout to better suit the task of telling the time.
4. Add an image resource to the project
5. Display that image using a combination of ``GBitmap`` and ``BitmapLayer``.
From there you can add more images, change the text font and size and more to
further improve the look of the watchface. In future posts you will learn how to
use ``Timer``s and ``Animation``s to make it even better!
###Source Code
Just like last time, the full source code file can be found
[here](https://gist.github.com/C-D-Lewis/17eb11ab355950595ca2) and directly
imported into CloudPebble
[as a new project](https://cloudpebble.net/ide/gist/17eb11ab355950595ca2). If
your code doesn't compile, have a look at this and see where you may have gone
wrong.
Send any feedback or questions to [@PebbleDev](http://twitter.com/PebbleDev) or
[@Chris_DL](http://twitter.com/Chris_DL).

View file

@ -0,0 +1,122 @@
---
# 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: Getting Ready for Automatic App Updates
author: katharine
tags:
- Freshly Baked
---
We are pleased to announce that we will soon be automatically updating apps installed from the Pebble
appstore. We believe this will be a significant improvement to your ability to get your work into
the hands of your users, as well as to the user experience.
However, as developers, you need to make sure that your app is ready for these updates. In
particular, you will need to make sure that your app conforms to our new version numbering, and
that it can tolerate persistent storage left over from old app versions.
Version Numbers
---------------
To prepare for automatic app updates, we have imposed new requirements on the format of app
version numbers. The new format is:
<center>
<p><strong>major.minor</strong></p>
</center>
Where minor is optional, and each component is considered independently (so 2.10 is newer than 2.5).
For instance, all of the following are valid version codes:
* 1.0
* 2.3
* 0.1
* 2
However, neither of the following are:
* ~~1.0.0~~
* ~~2.5-beta6~~
We will automatically upgrade the app if the version number from the watch is less than the latest
released version on the appstore. If old versions of your app contained a "bugfix" number, we will
use a number truncated to the major.minor format — so "1.0.7" will be taken as "1.0".
Persistent Storage
------------------
Automatic updates will retain persistent storage between app versions. This ensures that your users
will not be frustrated by updates silently deleting their data and settings. However, this puts the
onus on you as developers to ensure your app behaves well when presented with an old version of
its persistent storage. If your app does not already use persistent storage, you can ignore this
section.
We recommend that you do this by versioning your persistent storage, and incrementing the version
every time its structure changes.
The easiest way to do this versioning will be to create a new key in your persistent storage
containing the app version. If you have not already versioned your storage, you should simply check
for the version key and assume its absence to mean "version zero".
Once you know the "storage version" of your app, you can perform some migration to make it match
your current format. If the version is too old, or you have multiple "version zero" formats that you
cannot otherwise distinguish, you may instead just delete the old keys - but keep in mind that this
is a poor user experience, and we recommend avoiding it wherever possible.
Ultimately, you might have code that looks something like this:
```c
#define STORAGE_VERSION_KEY 124 // any previously unused value
#define CURRENT_STORAGE_VERSION 3
// ...
static void clear_persist(void) {
// persist_delete all your keys.
}
static void read_v2_persist(void) {
// Read the old v2 format into some appropriate structure
}
static void read_v1_persist(void) {
// Read the old v1 format into some appropriate structure.
}
static void migrate_persist(void) {
uint32_t version = persist_read_int(STORAGE_VERSION_KEY); // defaults to 0 if key is missing.
if(version > CURRENT_STORAGE_VERSION) {
// This is more recent than what we expect; we can't know what happened, so delete it
clear_persist();
} else if(version == 2) {
read_v2_persist();
store_persist();
} else if(version == 1) {
read_v1_persist();
store_persist();
} else if(version == 0) {
// Again, just delete this - perhaps we have multiple unversioned types, or 0 is just too
// long ago to be worth handling.
clear_persist();
}
}
```
Obviously, that is just an example; you will have to deal with your own situation as appropriate.
If you have further questions about getting ready for automatic app updates, let us know! [Contact us](/contact) or
[post on our forums](https://forums.getpebble.com/categories/developer-discussion).

View file

@ -0,0 +1,65 @@
---
# 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: FreeRTOS™ Code Revisions - Background Worker Edition
author: brad
tags:
- Down the Rabbit Hole
---
As part of our commitment to the open source community, Pebble is releasing its
recent modifications to the FreeRTOS project. Pebble has made a few minor
changes to FreeRTOS to enable Background Workers for PebbleOS 2.6 as well as to
make Pebble easier to monitor and debug.
The changes are available [as a tarball](http://assets.getpebble.com.s3-website-us-east-1.amazonaws.com/dev-portal/FreeRTOS-8.0.0-Pebble.2.tar.gz).
Below is a changelog of the modifications since the last time we released our
fork of the FreeRTOS code back in May.
* Added `UBaseType_t uxQueueGetRecursiveCallCount( QueueHandle_t xMutex )
PRIVILEGED_FUNCTION;` to queue.h
* Retrieves the number of times a mutex has been recursively taken
* FreeRTOS always tracked this internally but never made it available
externally
* Used to debug locking relating issues in PebbleOS
* Added `configASSERT_SAFE_TO_CALL_FREERTOS_API();`
* This macro can be used to assert that it is safe to call a FreeRTOS API.
It checks that the caller is not processing an interrupt or in a critical
section.
* See http://www.freertos.org/RTOS-Cortex-M3-M4.html for more details on
how interrupts interact with the FreeRTOS API
* Added `configASSERT_SAFE_TO_CALL_FREERTOS_FROMISR_API();`
* This macro can be used to assert that it is safe to call a FreeRTOS
"FromISR" API. It checks that the caller is at an appropriate interrupt
level.
* See http://www.freertos.org/RTOS-Cortex-M3-M4.html for more details on
how interrupts interact with the FreeRTOS API
* Added `uintptr_t ulTaskGetStackStart( xTaskHandle xTask );` to task.h
* Retrieves the address of the start of the stack space
* Useful for implementing routines which check for available stack space
* Added `bool vPortInCritical( void );` to port.c
* Indicates if we're currently in a FreeRTOS critical section
* Used to implement `configASSERT_SAFE_TO_CALL_FREERTOS_API();`
* Fixed an issue with vPortStoreTaskMPUSettings that occurred when more than
one task wanted to configure MPU regions
* This bug was encountered when adding support for Background Workers in
FW 2.6
Pebble would like to again extend its thanks to FreeRTOS and its community.
We'll be releasing additional updates as we continue to modify FreeRTOS in the
future. If you have any questions, dont hesitate to [contact us](/contact).

View file

@ -0,0 +1,149 @@
---
# 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: Displaying remote images in a Pebble app
author: thomas
tags:
- Beautiful Code
---
> A picture is worth a thousand words.
The old adage applies just as well to your Pebble apps! One of the most common
requests when [we attend hackathons](/community/events/) is "How do I transfer an image
from the Internet to my Pebble app?".
Today we introduce a new example that demonstrates downloading a PNG file from
the Internet and loading it on Pebble. We will also cover how to prepare your
images so that they take as little memory as possible and load quickly on Pebble.
The code is available in our
[pebble-examples github account][pebble-faces]. Continue reading for
more details!
## Downloading images to Pebble
The first step to display a remote image is to download it onto the phone. To do
this we built a simple library that can be easily reused: NetDownload. It is
based on [PebbleKit JavaScript](/guides/communication/using-pebblekit-js) and
``AppMessage``. The implementation is in
[`pebble-js-app.js`][pebble-js-app.js] for the JavaScript part and in
[`netdownload.c`][netdownload.c] for the C part.
Let's walk through the download process:
- The C application initializes the library with a call to
`netdownload_initialize()`. The library in turns initializes the AppMessage
framework.
- The [`show_next_image()` function][netdownload-call] calls `netdownload_request(char *url)` to
initiate a download. This function sends an AppMessage to the JavaScript with
two elements. One is the URL to be downloaded and the second one is the
maximum transmission size supported by the watch (this is provided by
``app_message_inbox_size_maximum()``).
- The JavaScript receives this message, tries to download the resource from
the Internet (via `downloadBinaryResource()`) and saves it as byte array.
- The image is then split into chunks (based on the maximum transmission size)
and sent to the watch. The first message contains the total size of the image
so the watchapp can allocate a buffer large enough to receive the entire
image. We use the JavaScript success and error callbacks to resend chunks as necessary.
- After the last chunk of data, we send a special message to tell the app that
we are done sending. The app can then check that the size matches what was
announced and if everything is ok, it calls the download successful callback
that was defined when initializing the library.
## Loading PNG images on Pebble
Instead of converting images to the native PBI format of Pebble, this example
transmits the image in PNG format to the watch and uses a PNG library to decompress
the image and to display it on the screen. The PNG library used is
[uPNG by Sean Middleditch and Lode Vandevenne][upng].
This approach is very convenient for multiple reasons:
- It is often easier to generate PNG images on your server instead of the
native PBI format of Pebble.
- PNG images will be smaller and therefore faster to transfer on the network
and over Bluetooth.
It does have one drawback though and that is that the PNG library uses
approximately 5.5kB of code space.
In the NetDownload callback we receive a pointer to a byte-array and its length.
We simply pass them to `gbitmap_create_with_png_data()` (provided by `png.h`)
and in return we get a ``GBitmap`` that we can display like any other
``GBitmap`` structure.
## Preparing your images
Because your PNG file will be transferred to Pebble and loaded into the limited
memory available on the watch, it is very important to make sure that the PNG is
as small as possible and does not contain useless information.
Specifically:
- The image should be 144x168 pixels (fullscreen) or smaller
- It should be in black and white
- It should not contain any metadata (like the author name, gps location of the
pictures, etc)
To prepare your image, we recommend using Image Magick (careful! Graphics Magick
is a different library and does not support some of the options recommended
below, it is important to use Image Magick!)
convert myimage.png \
-adaptive-resize '144x168>' \
-fill '#FFFFFF00' -opaque none \
-type Grayscale -colorspace Gray \
-colors 2 -depth 1 \
-define png:compression-level=9 -define png:compression-strategy=0 \
-define png:exclude-chunk=all \
myimage.pbl.png
Notes:
- `-fill #FFFFFF00 -opaque none` makes the transparent pixels white
- `-adaptive-resize` with `>` at end means resize only if larger, and maintains aspect ratio
- we exclude PNG chunks to reduce size (like when image was made, author)
If you want to use [dithering](http://en.wikipedia.org/wiki/Dither) to simulate Gray, you can use this command:
convert myimage.png \
-adaptive-resize '144x168>' \
-fill '#FFFFFF00' -opaque none \
-type Grayscale -colorspace Gray \
-black-threshold 30% -white-threshold 70% \
-ordered-dither 2x1 \
-colors 2 -depth 1 \
-define png:compression-level=9 -define png:compression-strategy=0 \
-define png:exclude-chunk=all \
myimage.pbl.png
For more information about PNG on Pebbles, how to optimize memory usage and tips
on image processing, please refer to the
[Advanced techniques videos](/community/events/developer-retreat-2014/)
from the Pebble Developer Retreat 2014.
## Connecting the pieces
The main C file (`pebble-faces.c`) contains a list of images to load and
everytime you shake your wrist it will load the next one. Take a few minutes to
test this out and maybe this technique will find its way into your next watchface!
[pebble-faces]: {{site.links.examples_org}}/pebble-faces
[netdownload.c]: {{site.links.examples_org}}/pebble-faces/blob/master/src/netdownload.c
[pebble-js-app.js]: {{site.links.examples_org}}/pebble-faces/blob/master/src/js/pebble-js-app.js
[netdownload-call]: {{site.links.examples_org}}/pebble-faces/blob/master/src/pebble-faces.c#L25
[upng]: https://github.com/elanthis/upng

View file

@ -0,0 +1,222 @@
---
# 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: Send a Smile with Android Actionable Notifications
author: thomas
date: 2014-12-19
tags:
- Beautiful Code
banner: /images/blog/1219-header.jpg
---
Just a few days ago, we released [beta version 2.3 of our Android
Application][android-beta] with support for actionable notifications. If you
have not tested it already, [enroll in our beta channel][beta-channel] and try
it out for yourself!
Notifications have always been a key use case for Pebble, and we are excited by
this new feature which is going to change the way you look at
notifications. With actionable notifications Pebble not only informs you
about relevant events, users can now interact with them and choose from actions
you as an Android developer attach to them.
When connected to an Android device, Pebble will show all wearable actions, just
like any Android Wear device. While supporting wearable notifications is easy
we have found that there are still a number of mobile apps who miss the opportunity
to extend their reach to the wrist. Don't let your app be one of those!
In this post, we will describe what you can do with actions on wearable devices
and how to add them to your Android notifications.
## Actionable Notifications
Android actionable notifications on the phone were introduced with Android 4.1.
Developers specify different actions per notification and users can react to them
by pushing a button on the phone's screen.
By default those actions are shown in the notification area.
You can make those actions visible to wearable devices and you even have the
option to collect data from the user before triggering the action. For example,
a "Reply" action can offer a list of pre-defined messages that the user can
choose from. On Pebble, this includes a long list of nice emojis!
To take advantage of actionable notifications on Pebble and Android Wear
devices, you need to do two things:
- Add support for notification actions in your application
- Declare which ones you want to make available on wearable device
Let's take a look at how to do this.
## Pushing a Notification to the Watch
All notifications start with the Android Notification Builder. A best practice
is to use the `NotificationCompat` class from the [Android Support
libraries][android-support-lib] which will automatically handle backwards
compatibility issues. For example, action buttons are only available in Android
4.1 and up; the library will automatically make sure your code stays compatible
with older Android phones and hide the buttons if necessary.
If you have not installed the Android Support Library, you can [follow these
instructions][android-support-lib-setup] or simply remove the `Compat`
suffix to the classes we use in this post.
To prepare a notification, you create an instance of a builder and define some
properties on it. At the very least, you need an icon, a title and some text:
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentTitle("Wearable Pomodoro");
builder.setContentText("Starting the timer ...");
To push the notification, get a hold of the system notification manager:
NotificationManager notifManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
And finally send the notification:
notifManager.notify(42, builder.build());
> The first parameter is a notification id that you can use to edit or cancel the
notification later.
Once you give your app the permission to send notifications to your Pebble
via the notifications menu in the Pebble Android app,
this notification appears with the default _Dismiss_ action.
![A simple notification on an Android device and a Pebble](/images/blog/1219-an-notif.png)
If you press _Dismiss_ on Pebble, the notification will be dismissed on the
watch **and** on the phone. This is a nice start, but there's so much more
control you can give to your users with custom actions.
> While custom actions work for Android 4.1 and above,
> dismiss actions on Pebble are only visible from Android 4.3 onwards.
## Adding Actions to Notifications
Let's add a custom action to our notification.
The first step is to define what it will do when it is triggered.
Actions can execute any of the standard Android intents (Activity, Service or
Broadcast). You prepare the intent, wrap it in a `PendingIntent` instance and it
will be triggered if the user selects this action.
This is what this looks like with a broadcast intent:
// Create a new broadcast intent
Intent pauseIntent = new Intent();
pauseIntent.setAction(ACTION_PAUSE);
// Wrap the intent into a PendingIntent
PendingIntent pausePendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, pauseIntent, 0);
> Note here that `ACTION_PAUSE` is simply a string that we defined as a constant
> in the class:
>
> static final String ACTION_PAUSE = "com.getpebble.hacks.wearablepomodoro.PAUSE";
Now that we have the pending intent, we can create the actual
`Notification.Action` instance using an action builder:
NotificationCompat.Action pauseAction =
new NotificationCompat.Action.Builder(R.drawable.ic_launcher, "Pause", pausePendingIntent).build();
To make this action available on the Android notification view, you can add it
directly to the notification:
builder.addAction(pauseAction);
Finally, **and this is the only _wearable_ specific step**, to make the action
available on Pebble and Android Wear, you **must** declare it as available to
wearable devices:
builder.extend(new NotificationCompat.WearableExtender().addAction(pauseAction));
We now have an action available on Android and on Pebble:
![An action shown on the phone and the watch](/images/blog/1219-an-pause.png)
You can add multiple actions as you want. If you have more than one, Pebble will
show a "More..." menu and display all the actions in a list.
> The option to "Open on Phone" is not available in this example because we did
> not define a default action for the notification. It will appear automatically
> if you do.
## User Input on Action
In some cases a simple choice of actions is not enough. You may want to collect
some information from the user, for example a reply to a text message.
On Pebble, the user will be presented with a list of possible replies that you
can customize. Such actions always present a list of emojis, too.
In Android lingo, this is an
action with support for remote input and it is trivial to set up and configure:
String[] excuses = { "Coworker", "Facebook", "Exercise", "Nap", "Phone", "N/A" };
RemoteInput remoteInput = new RemoteInput.Builder(KEY_INTERRUPT_REASON)
.setLabel("Reason?")
.setChoices(excuses)
.build();
NotificationCompat.Action interruptAction =
new NotificationCompat.Action.Builder(R.drawable.ic_launcher, "Interrupt", replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
> Pebble will hide input actions if you set `.setAllowFreeFormInput(false)`.
Don't forget to add this action to the WearableExtender to **make it visible on
wearable devices**:
builder.extend(new Notification.WearableExtender().addAction(pauseAction).addAction(replyAction));
And this is what it looks like on Pebble:
![Action with remote input on Pebble](/images/blog/1219-an-pebble-interrupt.png)
## Caveat
One useful caveat to know about when you are sending notifications to Pebble is
that the watch will automatically de-duplicate identical notifications. So if
the title and content of your notification is identical to another notification
recently displayed, Pebble will not show it.
This is very useful in the field to avoid spamming the user when notifications
are updated but as a developer you may run into this when you test your app.
If you do, just make sure something in your message changes between each notification.
## That's All Folks!
As you can see there is nothing magical about actionable notifications and they are
very easy to add to your app. We look forward to more Android apps supporting
useful wearable actions and taking advantage of remote input.
> **Further Reading on Android Notifications**
>
> For more information, we suggest you take a look at the [Android Notifications
Design Guide][android-patterns-notifications] and the associated [developer
guide][android-development-notifications]. Both are great resources to make the
most of notifications!
[android-beta]: https://blog.getpebble.com/2014/12/16/ad-23/
[beta-channel]: /blog/2014/06/12/Android-Beta-Channel/
[android-patterns-notifications]: http://developer.android.com/design/patterns/notifications.html
[android-development-notifications]: http://developer.android.com/guide/topics/ui/notifiers/notifications.html
[android-support-lib]: http://developer.android.com/tools/support-library/
[android-support-lib-setup]: http://developer.android.com/tools/support-library/setup.html

View file

@ -0,0 +1,576 @@
---
# 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 Emulator 1/2 - QEMU for Pebble
author: Ron Marianetti
tags:
- Down the Rabbit Hole
date: 2015-01-30
---
This is another in a series of technical articles provided by the members of the
Pebble software engineering team. This article describes some recent work done
at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short
for Quick EMUlator, is a generic, open source machine emulator and virtualizer).
> This post is part 1 of 2, and covers the development of the Pebble QEMU
> emulator itself. Read
> [*Pebble Emulator 2/2 - JavaScript and CloudPebble*][1]
> for more information on PebbleKit JS emulation and
> embedding the emulator in CloudPebble.
An early implementation of this emulator was used internally at Pebble over a
year ago and was mentioned in a previous article published in April, 2014,
[*How Pebble Converted 135,070 Customized Watchfaces for Pebble OS v2.0.*][4]
However, it had languished due to lack of attention, and somewhere along the
line changes made to the firmware had broke it altogether.
An emulator has many benefits, not only for outside developers but also for the
internal team:
* New software can be loaded, executed, and tested much faster on the emulator
than on actual hardware.
* It greatly facilitates automated testing, due to the faster speed of
execution, convenience, and ease of scripting.
* For internal use, the emulator can be built to run firmware images that
wouldnt fit into the limited memory of the real hardware, enabling engineers
to develop and test new features with extra debugging features included.
* Some tasks, like testing localization changes, can be easily performed in
parallel by launching a different emulator instance for each language.
[The QEMU open source project][2] was written by Fabrice Bellard. QEMU is a very
flexible and powerful code base capable of emulating a wide variety CPUs, one of
which is the ARM Cortex M3 that is inside the STM32F2xx microcontroller used in
the Pebble. Building upon the core QEMU project as a base, Andre Beckus created
a [fork][3] that adds in support for the hardware peripherals of the STM32F1xx
series of microcontrollers. Pebble further built upon Andre Beckus fork for its
emulator and added in support for the STM32F2xx series of microcontrollers as
well as the specific hardware components in the Pebble that are outside the
microcontroller, like the display and buttons. The result is a full-featured
Pebble emulator capable of executing Pebble firmware images and native 3rd party
applications.
## Interacting With the Emulator
When you launch the Pebble emulator on a host machine you are presented with a
window displaying the contents of the Pebble display. You can interact with it
using the arrow keys from the keyboard, which act as the 4 buttons on the Pebble
(“back”, “up”, “down”, and “select”). You can also load in and run, unmodified,
any third-party watchface or watchapp written for the Pebble.
A huge part of the value of the Emulator is in the additional features it
provides for development and testing purposes, like being able to interact with
the Pebble through a terminal and debug it using gdb. These capabilities are not
even possible with a standard shipping Pebble. Before the emulator, the only way
engineers at Pebble could accomplish this was to use custom “big boards”, which
are specially built boards that include the standard Pebble components with the
addition of a USB port and associated circuitry.
The emulator exposes three socket connections to the outside world. The first,
the gdb socket, is built into the base QEMU framework itself and allows one to
connect and debug the emulated CPU using gdb. The second, the console socket, is
specific to Pebble emulation and is a simple terminal window into which you can
see print output and issue commands to the Pebble OS. The third, the qemu
channel, is also specific to Pebble emulation and is used for sending Bluetooth
traffic and various other hardware sensor information such as the battery level,
compass direction, and accelerometer readings.
## The GDB Socket
Built into QEMU is a gdb remote server that listens by default on port 1234 for
connections from gdb. This remote server implements most of the basic debugging
primitives required by gdb including inspecting memory, setting breakpoints,
single-stepping, etc.
A critical aspect to debugging firmware running on the Pebble is the ability to
see what each of the threads in the operating system is doing at any time. For
this, gdb provides the “info threads” and related commands like “thread apply
all backtrace”, etc. In order to gather the information about the threads
though, the gdb remote server needs to understand where the thread information
is stored in memory by the remote target, which of course is operating system
specific.
The built-in gdb remote server implemented by QEMU does not have this knowledge
because it is specific to the Pebble. Internally, Pebble uses FreeRTOS for
managing threads, mutexes, semaphores, etc. Since the location of this
information in memory could easily change in different versions of the Pebble
firmware, it did not seem appropriate to incorporate it into QEMU. Instead, we
took the approach of creating a proxy process that sits between gdb and the gdb
remote server implemented by QEMU. This proxy forwards most generic requests
from gdb unmodified to QEMU and returns the responses from QEMU back to gdb. If
however it sees a request from gdb that is thread related, it does the handling
in the proxy itself. Interpreting the thread command generally involves issuing
a series of primitive memory read requests to QEMU to gather the information
stored by FreeRTOS. This design ensures that the QEMU code base is isolated from
the operating system dependencies of the Pebble firmware that could change from
version to version.
## The Console Socket
Console input and output has always been built into the Pebble OS but was
traditionally only accessible when using one of the engineering “big boards”.
There are function calls in Pebble OS for sending output to the console and a
simple interpreter and executor that parses input commands. This console output
and the available commands are invaluable tools when developing and debugging
the Pebble firmware.
Routing this console output to a TCP socket leverages a very powerful and
flexible feature of QEMU, which is the ability to create up to four virtual
serial devices. Each serial device can be associated with a file, pipe,
communication port, TCP socket (identified by port number), etc. This capability
is exposed through the `-serial` command line option of QEMU. When we launch
QEMU to emulate a Pebble, we create one of these serial devices for console use
and assign it to a socket. On the emulated Pebble side, the firmware is simply
accessing a UART peripheral built into the STM32F2xx and has no knowledge of the
external socket connection. In QEMU, the code that emulates that UART device is
given a reference to that virtual serial device so that it can ferry data
between the two.
## The QEMU Channel Socket
The third socket is the Pebble QEMU channel (PQ channel). This channel is used
for a number of purposes that are specific to the Pebble running in the
emulator. For example, we send data through this channel to give the Pebble
custom sensor readings like accelerometer, compass, battery level, etc. We also
use this channel to send and receive Bluetooth traffic.
We have decided to take the approach of creating custom builds of the firmware
for use in the emulator, which frees us from having to make the emulator run the
exact same firmware image that runs on an actual hardware. Of course it is
advantageous to minimize the differences as much as possible. When it comes to
each of the hardware features that needs to be emulated, a judgment call thus
has to be made on where to draw the line try to emulate the hardware exactly
or replace some of the low level code in the firmware. Some of the areas in the
firmware that we have decided to modify for the emulator are the areas that
handle Bluetooth traffic, accelerometer readings, and compass readings, for
example.
There is some custom code built into the firmware when it is built for QEMU to
support the PQ channel. It consists of two components: a fairly generic
STM32F2xx UART device driver (called “qemu_serial”) coupled with some logic to
parse out “Pebble QEMU Protocol” (PQP) packets that are sent over this channel
and pass them onto the appropriate handler. Every PQP packet is framed with a
PQP header containing a packet length and protocol identifier field, which is
used to identify the type of data in the packet and the handler for it.
## Bluetooth Traffic
In the Pebble OS, there is a communication protocol used which is called, not
surprisingly, “Pebble Protocol”. When running on a real Pebble, this protocol
sits on top of Bluetooth serial and is used for communication between the phone
and the Pebble. For example, this protocol is used to install apps onto the
Pebble, get the list of installed apps, set the time and date on the watch, etc.
The pebble SDK comes with a command line tool called simply “pebble” which also
allows you to leverage this protocol and install watch apps directly from a host
computer. When using the pebble tool, the tool is actually sending Pebble
protocol packets to your phone over a WebSocket and the Pebble app on the phone
then forwards the packets to the Pebble over Bluetooth. On the Pebble side, the
Bluetooth stack collects the packet data from the radio and passes it up to a
layer of code in Pebble OS that interprets the Pebble Protocol formatted packet
and processes it.
Rather than try and emulate the Bluetooth radio in the emulator, we have instead
decided to replace the entire Bluetooth stack with custom code when building a
firmware image for QEMU. As long as this code can pass Pebble protocol packets
up, the rest of the firmware is none the wiser whether the data is coming from
the Bluetooth radio or not. We chose this approach after noticing that most
other emulators (for Android, iOS, etc.) have chosen not to try and emulate
Bluetooth using the radio on the host machine. Apparently, this is very
difficult to get right and fraught with problems.
To support the emulator, we have also modified the pebble tool in the SDK to
have a command line option for talking to the emulator. When this mode is used,
the pebble tool sends the Pebble Protocol packets to a TCP socket connected to
the PQ channel socket of the emulator. Before sending the Pebble Protocol packet
to this socket, it is framed with a PQP header with a protocol ID that
identifies it as a Pebble protocol packet.
## Accelerometer Data
Data for the accelerometer on the Pebble is also sent through the PQ channel. A
unique PQP protocol ID is used to identify it as accelerometer data. The pebble
tool in the SDK includes a command for sending either a fixed reading or a
series of accelerometer readings from a file to the emulator.
An accelerometer PQP packet includes a series of one or more accelerometer
reading. Each reading consists of three 16-bit integers specifying the amount of
force in each of the X, Y and Z-axes.
On actual hardware, the accelerometer is accessed over the i2c bus in the
STM32F2XX. A series of i2c transactions are issued to set the accelerometer mode
and settings and when enough samples are ready in its FIFO, it interrupts the
CPU so that the CPU can issue more i2c transactions to read the samples out.
The current state of the emulator does not yet have the STM32F2XX i2c peripheral
fully implemented and does not have any i2c devices, like the accelerometer,
emulated either. This is one area that could definitely be revisited in future
versions. For now, we have taken the approach of stubbing out device drivers
that use i2c when building the firmware for QEMU and plugging in custom device
drivers.
Control gets transferred to the custom accelerometer device driver for QEMU
whenever a PQP packet arrives with accelerometer data in it. From there, the
driver extracts the samples from the packet and saves them to its internal
memory. Periodically, the driver sends an event to the system informing it that
accelerometer data is available. When the system gets around to processing that
event, it calls back into the driver to read the samples.
When an application subscribes to the accelerometer, it expects to be woken up
periodically with the next set of accelerometer samples. For example, with the
default sampling rate of 25Hz and a sample size of 25, it should be woken up
once per second and sent 25 samples each time. The QEMU accelerometer driver
still maintains this regular wakeup and feed of samples, but if no new data has
been received from the PQ channel, it simply repeats the last sample -
simulating that the accelerometer reading has not changed.
## Compass Data
Data for the compass on the Pebble is also sent through the PQ channel with a
unique PQP protocol ID. The pebble tool in the SDK includes a command for
sending compass orientation (in degrees) and calibration status.
On the Pebble side, we have a custom handler for PQP compass packets that simply
extracts the heading and calibration status from the packet and sends an event
to the system with this information which is exactly how the real compass
driver hooks into the system.
## Battery Level
Data for the battery level on the Pebble is also sent through the PQ channel
with a unique PQP protocol ID. The pebble tool in the SDK includes a command for
setting the battery percent and the plugged-in status.
On the Pebble side, we have a custom handler for PQP battery packets inside a
dedicated QEMU battery driver. When a PQP battery packet arrives, the driver
looks up the battery voltage corresponding to the requested percent and saves
it. This driver provides calls for fetching the voltage, percent and plugged-in
status.
On real hardware, the battery voltage is read using the analog to digital
converter (ADC) peripheral in the STM32F2xx. For now, the emulator takes the
approach of stubbing out the entire battery driver but in the future, it might
be worth considering emulating the ADC peripheral better and then just feed the
PQP packet data into the emulated ADC peripheral.
## Taps
On the real hardware, the accelerometer has built-in tap detection logic that
runs even when the FIFO based sample-collecting mode is turned off. This tap
detection logic is used to support features like the tap to turn on the
backlight, etc.
Data for the tap detection on the Pebble is also sent through the PQ channel
with a unique PQP protocol ID. The pebble tool in the SDK includes a command for
sending taps and giving the direction (+/-) and axis (X, Y, or Z).
On the Pebble side, we have a custom handler for PQP tap packets that simply
sends an event to the system with the tap direction and axis which is exactly
what the hardware tap detection driver normally does.
## Button Presses
Button presses can be sent to the emulated Pebble through two different
mechanisms. QEMU itself monitors key presses on the host system and we hook into
a QEMU keyboard callback method and assert the pins on the emulated STM32F2xx
that would be asserted in real hardware depending on what key is pressed.
An alternate button pressing mechanism is exposed through the PQ channel and is
provided primarily for test automation purposes. But here, in contrast to how we
process things like accelerometer data, no special logic needs to be executed in
the firmware to handle these button PQP packets. Instead, they are processed
entirely on the QEMU side and it directly asserts the appropriate pin(s) on the
emulated STM32F2xx.
A module in QEMU called “pebble_control” handles this logic. This module
essentially sits between the PQ channel serial device created by QEMU and the
UART that we use to send PQP packets to the emulated Pebble. It is always
monitoring the traffic on the PQ channel. For some incoming packets, like button
packets, the pebble_control module intercepts and handles them directly rather
than passing them onto the UART of the emulated Pebble. To register a button
state, it just needs to assert the correct pins on the STM32F2xx depending on
which buttons are pressed. The button PQP packet includes the state of each
button (pressed or not) so that the pebble_control module can accurately reflect
that state among all the button pins.
## Device Emulation
So far, we have mainly just talked about how one interacts with the emulator and
sends sensor data to it from the outside world. What exactly is QEMU doing to
emulate the hardware?
## QEMU Devices
QEMU is structured such that each emulated hardware device (CPU, UART, flash
ROM, timer, ADC, RTC, etc.) is mirrored by a separate QEMU device. Typically,
each QEMU device is compiled into its own module and the vast majority of these
modules have no external entry points other than one that registers the device
with a name and a pointer to an initialization method.
There are far too many intricacies of QEMU to describe here, but suffice it to
say that the interface to each device in QEMU is standardized and generically
defined. You can map an address range to a device and also assign a device one
or more interrupt callbacks. Where a device would normally assert an interrupt
in the real hardware, it simply calls an interrupt callback in QEMU. When the
emulated CPU performs a memory access, QEMU looks up to see if that address maps
to any of the registered devices and dispatches control to a handler method for
that device with the intended address (and data if it is a write).
The basic mode of communication between devices in QEMU is through a method
called `qemu_set_irq()`. Contrary to its name, this method is not only used for
generating interrupts. Rather, it is a general-purpose method that calls a
registered callback. One example of how this is used in the Pebble emulation is
that we provide a callback to the pin on the emulated STM32F2xx that gets
asserted when the vibration is turned on. The callback we provide is a pointer
to a method in the display device. This enables the display device to change how
it renders the Pebble window in QEMU based on the vibration control. The IRQ
callback mechanism is a powerful abstraction because an emulated device does not
need to know who or what it is connected to, it simply needs to call the
registered callback using `qemu_set_irq()`.
## Pebble Structure
We create multiple devices to emulate a Pebble. First of course there is the
CPU, which is an ARM v7m variant (already implemented in the base code of QEMU).
The STM32F2XX is a microcontroller containing an ARM v7m CPU and a rather
extensive set of peripherals including an interrupt controller, memory
protection unit (MPU), power control, clock control, real time clock, DMA
controller, multiple UARTS and timers, I2C bus, SPI bus, etc. Each of these
peripherals is emulated in a separate QEMU device. Many of these peripherals in
the STM32F2xx behave similarly with those in the STM32F1xx series of
microcontroller implemented in Andre Beckus fork of QEMU and so could be
extended and enhanced where necessary from his implementations.
Outside of the STM32F2xx, we have the Pebble specific devices including the
display, storage flash, and buttons and each of these is emulated as a separate
QEMU device as well. For the storage flash used in the Pebble, which sits on the
SPI bus, we could use a standard flash device already implemented in QEMU. The
display and button handling logic however are custom to the Pebble.
## Device Specifics
Getting the Pebble emulation up and running required adding some new QEMU
devices for peripherals in the STM32F2XX that are not in the STM32F1XX and
extending and enhancing some of the existing devices to emulate features that
were not yet fully implemented.
To give a flavor for the changes that were required, here is a sampling: the DMA
device was more fully implemented to support 8 streams of DMA with their
associated IRQs; a bug in the NOR flash device was addressed where it was
allowing one to change 0s to 1s (an actual device can only change 1s to 0s
when programming); many of the devices were raising an assert for registers that
were not implemented yet and support for these registers needed to be added in;
the PWR device for the STM32F2xx was added in; wake up timer support was not
implemented in the RTC; many devices were not resetting all of their registers
correctly in response to a hardware reset, etc.
## Real Time Clock
One of the more challenging devices to emulate correctly was the real time clock
(RTC) on the STM32F2xx. Our first implementation, for example, suffered from the
problem that the emulated Pebble did not keep accurate time, quite a glaring
issue for a watch!
The first attempt at emulating the RTC device relied on registering a QEMU timer
callback that would fire at a regular interval. The QEMU framework provides this
timer support and will attempt call your register callback after the requested
time elapses. In response to this callback, we would increment the seconds
register in the RTC by one, see if any alarm interrupts should fire, etc.
The problem with this approach is that there is no guarantee that the QEMU timer
callback method will be called at the exact requested time and, depending on the
load on the host machine, we were falling behind sometimes multiple minutes in
an hour.
To address this shortcoming, we modified the timer callback to instead fetch the
current host time, convert it to target time, and then modify the registers in
the RTC to match that newly computed target time. In case we are advancing the
target time by more than one in any specific timer callback, we also need to
check if any alarm interrupts were set to fire in that interval. We also update
the RTC time based on the host time (and process alarms) whenever a read request
comes in from the target for any of the time or calendar registers.
Although conceptually simple, there were a number of details that needed to be
worked out for this approach. For one, we have to maintain an accurate mapping
from host time to target time. Anytime the target modifies the RTC registers, we
also have to update the mapping from host to target time. The other complication
is that the Pebble does not use the RTC in the “classic” sense. Rather than
incrementing the seconds register once per second, the Pebble OS actually runs
that RTC at 1024 increments per second. It does this so that it can use the RTC
to measure time to millisecond resolution. The emulation of the RTC thus needs
to also honor the prescaler register settings of the RTC to get the correct
ratio of host time to target time.
A further complication arose in the Pebble firmware itself. When the Pebble OS
has nothing to do, it will put the CPU into stop mode to save power. Going into
stop mode turns off the clock and an RTC alarm is set to wake up the CPU after
the desired amount of time has elapsed. On real hardware, if we set the alarm to
fire in *N* milliseconds, we are guaranteed that when we wake up, the RTC will
read that it is now *N* milliseconds later. When running in emulation however,
quite often the emulation will fall slightly behind and by the time the emulated
target processes the alarm interrupt, the RTC registers will show that more than
*N* milliseconds have elapsed (especially since the RTC time on the target is
tied to the host time). Addressing this required modifying some of the glue
logic we have around FreeRTOS in order to fix up the tick count and wait time
related variables for this possibility.
## UART Performance
We rely heavily on the PQ channel to communicate with the emulated pebble. We
use it to send 3rd party apps to the emulated pebble from the host machine for
example and even for sending firmware updates to the emulated Pebble.
The first implementation of the UART device though was giving us only about
1Kbyte/second throughput from the host machine to the emulated Pebble. It turns
out that the emulated UART device was telling QEMU to send it only 1 character
at a time from the TCP socket. Once it got that character, it would make it
available to the emulated target, and once the target read it out, it would tell
QEMU it had space for one more character from the socket, etc.
To address this, the QEMU UART device implementation was modified to tell QEMU
to send it a batch of bytes from the socket at a time, then those bytes were
dispatched to the target one at a time as it requested them. This simple change
gave us about a 100x improvement in throughput.
## CPU Emulation Issues
Running the Pebble OS in QEMU ended up stressing some aspects of the ARM that
were not exercised as completely before, and exposed a few holes in the CPU
emulation logic that are interesting to note.
The ARM processor has two operating modes (handler mode and thread mode) and two
different stack pointers it can use (MSP and PSP). In handler mode, it always
uses the MSP and in thread mode, it can use either one, depending on the setting
of a bit in one of the control registers. It turns out there was a bug in the
ARM emulation such that the control bit was being used to determine which stack
pointer to use even when in handler mode. The interesting thing about this is
that because of the way the Pebble OS runs, this bug would only show up when we
tried to launch a 3rd party app for the first time.
Another, more subtle bug had to do with interrupt masking. The ARM has a BASEPRI
register, which can be used to mask all interrupts below a certain priority,
where the priority can be between 1 and 255. This feature was not implemented in
QEMU, so even when the Pebble OS was setting BASEPRI to mask off some of the
interrupts, that setting was being ignored and any interrupt could still fire.
This led to some intermittent and fairly hard to reproduce crashes in the
emulated Pebble whose source remained a mystery for quite a while.
We discovered an issue that if the emulated CPU was executing a tight infinite
loop, that we could not connect using gdb. It turns out that although QEMU
creates multiple threads on the host machine, only one is allowed to effectively
run at a time. This makes coding in QEMU easier because you dont have to worry
about thread concurrency issues, but it also resulted in the gdb thread not
getting a chance to run at all if the thread emulating the CPU had no reason to
exit (to read a register from a peripheral device for example). To address this,
we modified the CPU emulation logic to break out at least once every few hundred
instructions to see if any other thread had work to do.
As briefly mentioned earlier, the ARM has a stop mode that is used for power
savings and the Pebble OS uses this mode extensively. Going into stop mode turns
off the clock on the CPU and most peripherals until an alarm interrupt fires.
There was a flaw in the initial implementation of stop mode in the emulator that
resulted in any and all interrupts being able to wake up the CPU. The emulated
Pebble ran just fine and the user would not notice any problems. However, QEMU
ended up using 100% of a CPU on the host machine at all times. When this bug was
addressed, the typical CPU load of QEMU went down to about 10%. Addressing this
bug was critical for us in order to efficiently host enough QEMU instances on a
server farm for use by CloudPebble.
Another challenge in the CPU emulation was figuring out how to correctly
implement standby mode for the STM32F2xx. In standby mode, all peripherals are
powered off and the Pebble sets up the CPU to only wake up when the WKUP pin on
the STM32F2xx is asserted. This mode is entered when the user chooses “Shut
Down” from the Pebble settings menu or automatically when the battery gets
critically low. In most cases, wake up sources for the CPU (like the RTC
peripheral, UARTs, timers, etc.) generate an interrupt which first goes to the
interrupt controller (NVIC) which is outside the core CPU. The NVIC then figures
out the priority of the interrupt and only wakes the CPU if that priority of
interrupt is not masked off. The WKUP pin functionality is unique in that it is
not a normal interrupt, cannot be masked, and instead of resulting in the
execution of an interrupt service routine, it results in a reset of the CPU.
Figuring out how to hook this up correctly into QEMU required quite a bit of
learning and investigation into the core interrupt handling machinery in QEMU
and understanding difference between that and the WKUP functionality.
## Window Dressing
A few of the many fun things about working on the emulator had to do with
finding creative ways to represent things such as the brightness of the
backlight and the status of the vibration output.
In the Pebble hardware, a timer peripheral is used to generate a PWM (Pulse
Width Modulated) output signal whose duty cycle controls the brightness of the
backlight. This allows the Pebble to gradually ramp up and down the brightness
of the backlight. In QEMU, the STM32F2xx timer device was enhanced to support
registering a QEMU IRQ handler that it would call whenever the PWM output value
was changed. In QEMU, when you call an interrupt handler callback using
`qemu_set_irq()`, you pass in an integer argument, allowing one to pass a scalar
value if need be. A “brightness” interrupt handler callback was added to the
Pebble display device and this callback was registered with the timer
peripheral. Once hooked up, this enabled the display to be informed whenever the
PWM output value changed. The display device implementation was then modified to
adjust the brightness (via the RGB color) of the pixels rendered to the display
based on the scalar value last sent to its brightness callback.
Whenever the Pebble firmware decides to turn on the vibration, it asserts one of
the GPIO (General Purpose IO) pins on the STM32F2xx. To implement this feature,
a “vibration” callback was added to the display device implementation and this
callback was registered with the GPIO device pin that gets asserted by the
firmware in order to vibrate. To implement the vibrate, the display device
repeatedly re-renders the contents of the Pebble screen to the window offset
plus or minus 2 pixels, giving the illusion of a vibrating window.
## Future Work
The Pebble emulator has already proven to be a great productivity enhancer in
its current state, and has a lot of potential for future enhancements and
improvements.
It would be interesting for example to investigate how well we can emulate I2C
and some of the I2C devices used in the Pebble. Doing this could allow us to use
more of the native device drivers in the firmware for things like the
accelerometer rather than having to plug in special QEMU based ones.
Another area for investigation revolves around Bluetooth. Currently, we replace
the entire Bluetooth stack, but another option worth investigating is to run the
native Bluetooth stack as-is and just emulate the Bluetooth chip used on the
Pebble. This chip communicates over a serial bus to the STM32F2xx and accepts
low-level HCI (Host Controller Interface) commands.
There is a lot of potential to improve the UI on the QEMU side. Currently, we
show only a bare window with the contents of the Pebble display. It would be a
great improvement for example to show a status area around this window with
clickable buttons. The status area could display helpful debugging information.
There is a lot of potential for enhanced debugging and profiling tools. Imagine
a way to get a trace of the last *N* instructions that were executed before a
crash, or improved ways to profile execution and see where performance can be
improved or power savings realized.
## More Information
Continue reading [*Pebble Emulator 2/2 - JavaScript and CloudPebble*][1]
for more information on PebbleKit JS emulation and embedding the emulator in
CloudPebble.
[1]: /blog/2015/01/30/Pebble-Emulator-JavaScript-Simulation/
[2]: http://wiki.qemu.org/Main_Page
[3]: http://beckus.github.io/qemu_stm32/
[4]: http://appdevelopermagazine.com/1313/2014/4/14/How-Pebble-Converted-135,070-Customized-Watchfaces-For-Pebble-OS-v2.0/

View file

@ -0,0 +1,433 @@
---
# 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 Emulator 2/2 - JavaScript and CloudPebble
author: katharine
tags:
- Down the Rabbit Hole
date: 2015-01-30
---
This is another in a series of technical articles provided by the members of the
Pebble software engineering team. This article describes some recent work done
at Pebble to develop a Pebble emulator based on the QEMU project (QEMU, short
for Quick EMUlator, is a generic, open source machine emulator and virtualizer).
> This post is part 2 of 2, and details the work undertaken to provide emulation
> of the PebbleKit JS and CloudPebble aspects of emulating a Pebble in its
> entirety. Read
> [*Pebble Emulator 1/2 - QEMU For Pebble*][9] for information on
> the creation of the Pebble Emulator.
For developers of third-party apps it is insufficient to emulate just the Pebble
itself, as most non-trivial apps also run JavaScript on the phone to provide the
watchapp with additional information. Furthermore, some apps are written
entirely in JavaScript using Pebble.js; it is important to support those as
well.
We therefore decided to implement support for running JavaScript in tandem with
the emulated Pebble. The JavaScript simulator is called [*pypkjs*][11].
## JavaScript Runtimes
Since our JavaScript environment primarily consists of standard HTML5 APIs, we
initially tried building on top of [PhantomJS][1]. However, we quickly ran into
issues with the very old version of WebKit it uses and a lack of flexibility in
implementing the functionality we needed, so we abandoned this plan.
Our second attempt was to use [node.js][2], but this proved impractical because
it was difficult to inject additional APIs into modules before loading them, by
which time they may have already tried to use them. Furthermore, some libraries
detected that they were running in node and behaved differently than they would
in the mobile apps; this discrepancy proved tricky to eliminate.
We ultimately chose to build directly on top of
[Googles V8 JavaScript Engine][3], making use of the [PyV8][4] Python bindings
to reduce the effort involved in writing our APIs. This gave us the flexibility
to provide exactly the set of APIs we wanted, without worrying about the
namespace already being polluted. PyV8 made it easy to define these without
worrying about the arcana of the C++ V8 interface.
## Threading and Event Handling
JavaScript is intrinsically single-threaded, and makes heavy use of events. In
order to provide event handling, we used [gevent][5] to provide support the key
support for our event loop using greenlets. The “main thread” of the interpreter
is then a single greenlet, which first evaluates the JavaScript file and then
enters a blocking event loop. The PebbleKit JS program is terminated when this
event loop ends. Any calls to asynchronous APIs will then spawn a new greenlet,
which will ultimately add a callback to the event queue to take effect on the
main thread.
PebbleKit JS provides for single and repeating timers, which can take either a
function to call or a string to be evaluated when the timer expires. We
implemented these timers by spawning a new greenlet that sleeps for the given
period of time, then places either the function call or a call to eval on the
event queue.
## HTML5 APIs
Since we are using V8 without any additions (e.g. from Chromium), none of the
standard HTML5 APIs are present. This works in our favour: we only support a
restricted subset for PebbleKit JS. Our Android and iOS apps differ in their
support, so we chose to make the emulator support only the common subset of
officially supported APIs: XMLHttpRequest, localStorage, geolocation, timers,
logging and performance measurement. We implemented each of these in Python,
exposing them to the app via PyV8. We additionally support standard JavaScript
language features; these are provided for us by V8.
## LocalStorage
LocalStorage provides persistent storage to PebbleKit JS apps. It is a tricky
API to implement, as it has properties that are unlike most objects in
JavaScript. In particular, it can be accessed using either a series of method
calls (`getItem`, `setItem`, `removeItem`, etc.), as well as via direct property
access. In either case, the values set should be immediately converted to
strings and persisted. Furthermore, iterating over the object should iterate
over exactly those keys set on it, without any additional properties. Correctly
capturing all of these properties took three attempts.
Our first approach was to directly provide a Python object to PyV8, which
implemented the python magic methods `__setattr__`, `__delattr__` and
`__getattr__` to catch attribute access and handle them appropriately. However,
this resulted in ugly code and made it impossible to correctly implement the
LocalStorage iteration behaviour as PyV8 does not translate non-standard python
iterators.
The second approach was to create a native JavaScript object using
Object.create, set the functions on it such that they would not appear in
iteration, and use an ECMAScript 6 (“ES6”) Observer to watch for changes to the
object that should be handled. This approach failed on two fronts. The primary
issue was that we could not catch use of the delete operator on keys set using
property accessors, which would result in values not being removed. Secondly,
Observers are asynchronous. This made it impossible to implement the immediate
cast-to-string that LocalStorage performs.
The final approach was to use an ES6 Proxy to intercept all calls to the object.
This enabled us to synchronously catc property accesses to cast and store them.
It also provided the ability to provide custom iteration behaviour. This
approach lead to a clean, workable and fully-compliant implementation.
## Timers
PebbleKit JS provides for single and repeating timers, which can take either a
function to call or a string to be evaluated when the timer expires. We
implemented these timers by spawning a new greenlet that sleeps for the given
period of time, then places either the function call or a call to eval on the
event queue.
## Geolocation
PebbleKit JS provides access to the phones geolocation facilities. According to
the documentation, applications must specify that they will use geolocation by
giving the location capability in their manifest file. In practice, the mobile
apps have never enforced this restriction, and implementing this check turned
out to break many apps. As such, geolocation is always permitted in the
emulator, too.
Since there is no geolocation capability readily available to the emulator, it
instead uses the MaxMind GeoIP database to look up the users approximate
location. In practice, this works reasonably well as long as the emulator is
actually running on the users computer. However, when the emulator is *not*
running on the users computer (e.g. when using CloudPebble), the result isnt
very useful.
## XMLHttpRequest
Support for XMLHttpRequest is primarily implemented using the Python
[*requests* library][6]. Since requests only supports synchronous requests, and
XMLHttpRequest is primarily asynchronous, we spawn a new greenlet to process the
send request and fire the required callbacks. In synchronous mode we join that
greenlet before returning. In synchronous mode we must also place the *creation*
of the events on the event queue, as creating events requires interacting with
V8, which may cause errors while the main greenlet is blocked.
## Pebble APIs
PebbleKit JS provides an additional Pebble object for communicating with the
watch and handling Pebble accounts. Unlike the HTML5 APIs, these calls have no
specification, instead having two conflicting implementations and often vague or
inaccurate documentation that ignores the behaviour of edge cases. Testing real
apps with these, especially those that do not strictly conform to the
documentation, has required repeated revisions to the emulated implementation to
match what the real mobile apps do. For instance, it is not clear what should be
done when an app tries to send the float *NaN* as an integer.
## Watch Communication
The PebbleKit JS runtime creates a connection to a TCP socket exposed by QEMU
and connected to the qemu_serial device. Messages from PebbleKit JS are
exclusively Pebble Protocol messages sent to the bluetooth channel exposed over
the Pebble QEMU Protocol. A greenlet is spawned to read from this channel.
The primary means of communication available to apps over this channel is
AppMessage, a mechanism for communicating dictionaries of key-value pairs to the
watch. These are constructed from the provided JavaScript object. It is possible
for applications to use string keys; these are replaced with integer keys from
the apps manifest file before sending. If no such key can be found an exception
is thrown. This is the documented behaviour, but diverges from the implemented
behaviour; both mobile apps will silently discard the erroneous key.
When messages are received, a new JavaScript object is created and the messages
parsed into JavaScript objects. Here we perform the reverse mapping, converting
received integers to string keys, if any matching keys are specified in the
apps manifest. An event is then dispatched to the event loop on the main
greenlet.
A method is also provided for showing a “simple notification”; again, it is not
clear what sort of notification this should be. This implementation sends an SMS
notification, which appears to be consistent with what the iOS app does.
## Configuration Pages
PebbleKit JS provides the option for developers to show a “configuration page”
in response to the user pressing a Settings button in the Pebble mobile app.
These configuration pages open a webview containing a user-specified webpage.
The mobile apps capture navigation to the special URL pebblejs://close, at
which point they dismiss the webview and pass the URL fragment to the PebbleKit
JS app.
However, our emulator does not have the ability to present a webview and handle
the custom URL scheme, so another approach is required. We therefore pass a new
query parameter, return_to, to which we pass a URL that should be used in
place of the custom URL scheme. Configuration pages therefore must be modified
slightly: instead of using the fixed URL, they should use the value of the
return_to parameter, defaulting to the old pebblejs://close URL if it is
absent.
When a URL is opened, the PebbleKit JS simulator starts a temporary webserver
and gives a URL for it in the return_to parameter. When that page is loaded,
it terminates the webserver and passes the result to the PebbleKit JS app.
## Exception Handling
There are three potential sources of exceptions: errors in the users
JavaScript, error conditions for which we generate exceptions (e.g. invalid
AppMessage keys), and unintentional exceptions thrown inside our JavaScript
code. In all cases, we want to provide the user with useful messages and
JavaScript stack traces.
PyV8 supports exceptions, and will translate exceptions between Python and
JavaScript: most exceptions from JavaScript will become JSErrors in Python, and
exceptions from Python will generally become Exceptions in JavaScript. JSErrors
have a stack trace attached, which can be used to report to the user. PyV8 also
has some explicit support for IndexErrors (RangeErrors in JavaScript),
ReferenceErrors, SyntaxErrors and TypeErrors. When such an exception passes the
Python/JavaScript boundary, it is converted to its matching type.
This exception conversion support causes a complication: when a support
exception crosses from JavaScript to Python, it is turned into a standard Python
exception rather than a JSError, and so has no stack trace or other JavaScript
information attached. Since many exceptions become one of those (even when
thrown from inside JavaScript), and all exception handling occurs in Python,
many exceptions came through without any useful stack trace.
To resolve this issue, we [forked PyV8][7] and changed its exception handling.
We now define new exceptions for each of the four supported classes that
multiple- inherit from their respective Python exceptions and JSError, and still
have JavaScript stack information attached. We can then catch these exceptions
and display exception information as appropriate.
Due to the asynchronous nature of JavaScript, and the heavily greenlet-based
implementation of pypkjs, we must ensure that every point at which we call into
developer-provided JavaScript does something useful with any exceptions that may
be thrown so that JavaScript traces can be passed back to the developer.
Fortunately, the number of entry points is relatively small: the event system
and the initial evaluation are the key points to handle exceptions.
## Sandboxing
While PyV8 makes it very easy to just pass Python objects into JavaScript
programs and have them treated like standard JavaScript objects, this support is
an approximation. The resulting objects still feature all the standard Python
magic methods and properties, which the JavaScript program can access and call.
Furthermore, Python has no concept of private properties; any object state that
the Python code has access to can also be accessed by the JavaScript program.
While this behaviour is good enough when working with JavaScript programs that
expect to be running in this environment, the programs that will be run here are
not expecting to run in this environment. Furthermore, they are untrusted; with
access to the runtime internals, they could potentially wreak havoc.
In order to present a cleaner interface to the JavaScript programs, we instead
define JavaScript extensions that, inside a closure, feature a native function
call. These objects then define proxy functions that call the equivalent
functions in the Python implementation. By doing this, we both present genuine
JavaScript objects that act like real objects in all ways, and prevent access to
the implementation details of the runtime.
## Emulation in CloudPebble
The majority of our developers use our web-based development environment,
CloudPebble. The QEMU Pebble emulator and PebbleKit JS simulator described above
are designed for desktop use, and so would not in themselves be useful to the
majority of our developers. Some arrangement therefore had to be made for those
developers.
We decided to run a cluster of backend servers which would run QEMU on behalf of
CloudPebbles users; CloudPebble would then interact with these servers to
handle input and display.
## Displaying the Screen
Displaying a remote framebuffer is a common problem; the most common solution to
this problem is VNCs Remote Framebuffer Protocol. QEMU has a VNC server built-
in, making this the obvious choice to handle displaying the screen.
CloudPebbles VNC client is based on [noVNC][8], an HTML5 JavaScript-based VNC
client. Since JavaScript cannot create raw socket connections, noVNC instead
connects to the QEMU VNC server via VNC-over-websockets. QEMU already had
support for this protocol, but crashed on receiving a connection. We made a
minor change to initialisation to resolve this.
While it would appear to make sense to use indexed colour instead of 24-bit true
colour for our 1-bit display, QEMU does not support this mode. In practice,
performance of the true colour display is entirely acceptable, so we did not
pursue this optimisation.
## Communicating With the Emulator
CloudPebble expects to be able to communicate with the watch to install apps,
retrieve logs, take screenshots, etc. With physical watches, this is done by
connecting to the phone and communicating over the Pebble WebSocket Protocol
(PWP). Due to restrictions on WebSocket connections within local networks,
CloudPebble actually connects to an authenticated WebSocket proxy which the
phone also connects to. In addition, this communication occurs over bluetooth —
but the PebbleKit JS runtime is already connected to the qemu_serial socket,
which can only support one client at a time.
We therefore chose to implement PWP in the PebbleKit JS simulator. This neatly
the issue with multiple connections to the same socket, closely mimics how the
real phone apps behave, and minimises the scope of the changes required to
CloudPebble. CloudPebbles use of a WebSocket proxy provides further opportunity
to imitate that layer as well, enabling us to take advantage of the existing
authentication mechanisms in CloudPebble.
The PebbleKit JS simulator was thus split out to have two runners; one that
provides the standard terminal output (sendings logs to stdout and interacting
with the users local machine) and one that implements PWP. The WebSocket runner
additionally hooks into the low-level send and receive methods in order to
provide the message echoing functionality specified by PWP. Since the emulator
requires some communication that is not necessary with real phones, there are
were some extensions added that are used only by the emulator. However, for the
most part, existing code works exactly as before once pointed to the new
WebSocket URL.
## Configuration Pages
The mechanism previously designed for configuration pages is only usable when
running locally. To trigger a configuration page, CloudPebble sends a request
using an extension to the PWP. If the PebbleKit JS app implements a
configuration page, it receives a response giving it the developers intended
URL. CloudPebble then inserts a return_to parameter and opens a new window with
the developers page. Once the page navigates to the return URL, the page is
closed and the configuration data sent back over the WebSocket.
Due to restrictions on how windows may communicate, CloudPebble must poll the
new window to discover if it has navigated to the return URL. Once the
navigation is detected, CloudPebble sends it a message and receives the
configuration data in reply, after which the window closes.
A further complication is pop-up blockers. These usually only permit window
opens in response to direct user action. Since we had to request a URL from the
PebbleKit JS app before we could open the window, an active popup blocker will
tend to block the window. We worked around this by detecting the failure of the
window to open and providing a button to click, which will usually bypass the
popup blocker.
## Input
Originally, button input from CloudPebble was performed by sending keypresses
over VNC directly to QEMU. However, this turned out to cause issues with key-
repeat and long button presses. Resolving these issues proved to be difficult,
so we instead avoided sending VNC keypresses at all. Instead, another extension
was added to the PWP that permitted sending arbitrary PQP messages to the
emulator. We then sent packets indicating the state of each button to the
emulator via PWP. However, mouse clicks tend to be quicker than Pebble watch
button presses, and the time that the buttons appeared to be pressed was too
short for the firmware to react. To avoid this problem, we rate limited
CloudPebble to send state changes no more rapidly than once every 100ms; more
rapid changes are queued to be sent later.
The channel for sending PQP messages over PWP is also used to set the battery
state and toggle bluetooth; in the future, we will also use it to set the
accelerometer and compass readings.
## Compass and Accelerometer Sensors
Most computers do not have a compass or an accelerometer — and even if they did,
it would be impractical to pick up the computer and shake it, tilt it, or rotate
it to test apps. To deal with this, we took advantage of a piece of hardware
owned by all Pebble owners, and likely most Pebble developers: their phones.
When developers want to use their phones to provide sensor data, a six-digit
code is generated and stored with the information required to connect to the
emulator. The user is prompted to open a short URL (cpbl.io) on their phone and
enter the code on that webpage. The code is looked up and, if found, a
connection to the emulator is established from the webpage on their phone. The
webpage then collects accelerometer and compass data using the
[HTML5 DeviceOrientation APIs][10] and streams it to the emulator.
The generated codes expire a few minutes after being generated.
## Emulator Management
CloudPebble must manage multiple emulators on potentially multiple hosts.
Management is split between CloudPebble itself and a [controller program][12]
responsible for managing the lifecycle of individual emulator instances.
CloudPebble is aware of a pool of emulator hosts. When a user requests an
emulator, it picks one at random and requests that its manager spin up an
emulator. If this is possible, it returns connection details for the emulator to
the client; if not (e.g. because that host has reached capacity), it picks
another host and tries again. If no hosts are available a failure message is
reported to the client and logged in our analytics system.
The manager program selects some unused ports and spawns instances of QEMU and
pypkjs configured to work together, and reports back a UUID and public port
numbers for the VNC and Pebble WebSocket Protocol servers. The manager then
expects to be pinged for that emulator periodically; if too long passes without
being pinged or either QEMU or pypkjs fail, the QEMU and pypkjs instances will
be terminated.
A complication arose when attempting to run this system over the Internet. Some
users, especially behind corporate firewalls, cannot make connections to the
non-standard ports that the manager was selecting. To avoid this issue, the
manager (which runs on the standard HTTPS port 443) can proxy connections to the
VNC and PWP websockets.
Finally, in order to restrict abuse and provide continuity across client
reloads, CloudPebble tracks emulators assigned to users in a Redis database. If
a user who already has an emulator requests one and CloudPebble can ping their
emulator, they are given the same instance again. A new instance can be
requested by explicitly killing the emulator in the CloudPebble UI, or closing
the client and waiting for the manager to time out and kill the emulator.
[1]: http://phantomjs.org
[2]: http://nodejs.org
[3]: https://code.google.com/p/v8/
[4]: https://code.google.com/p/pyv8/
[5]: http://www.gevent.org
[6]: http://docs.python-requests.org/en/latest/
[7]: https://github.com/pebble/pyv8
[8]: https://github.com/kanaka/noVNC
[9]: /blog/2015/01/30/Development-Of-The-Pebble-Emulator/
[10]: http://w3c.github.io/deviceorientation/spec-source-orientation.html
[11]: https://github.com/pebble/pypkjs
[12]: https://github.com/pebble/cloudpebble-qemu-controller

View file

@ -0,0 +1,239 @@
---
# 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: Bezier Curves and GPaths on Pebble
author: lukasz
tags:
- Beautiful Code
date: 2015-02-13
summary: |
A look at how to use Bezier curves and GPaths to efficiently draw complex
paths in your Pebble apps and watchfaces.
banner: /images/blog/bezier-banner.png
---
Drawing complex paths requires a lot of manual work on Pebble. Here I'll show
you how to do this efficiently and quickly using a Pebble-optimized ``GPath``
algorithm and Bezier curves.
## Why Bezier Curves?
Typically, when a developer wants to create curve-based graphics on Pebble he
has to use prepared bitmaps or complex GPaths (or generate paths with
[svg2gpath](http://gpathsvg.org/)). Both of those approaches have serious
downsides: bitmaps require resource space to store, memory to process and
rotating bitmaps is a complex operation. Detailed GPaths, while fast in
processing, can take a long time to design without special tools.
Bezier curves are a very interesting solution, which may seem computationally
excessive but solves the obstacles of both the previous methods. Because they're
so simple to use they are common elements in many graphic systems, primarly
vector based graphics.
## Drawing Bezier Curves
The mathematical formula for a Bezier curve can be found on
[Wikipedia](http://en.wikipedia.org/wiki/Composite_B%C3%A9zier_curve). We can
fairly easily turn it into C code, but we have to make a couple of important
decisions. The formula itself is linear and requires us to decide how many steps
we want to take in order to draw our Bezier curve. It's safe to assume that
calculating 1000 points on our Bezier curve should be enough to display a
continuous line on the small Pebbles screen.
```c
void naive_bezier(GContext *ctx, GPoint points[]) {
for (double t = 0.0; t<1.0; t += 0.001) {
double tx = pow_d(1-t, 3) * points[0].x + 3 * t * pow_d(1-t, 2) * points[1].x +
3 * pow_d(t, 2) * (1-t) * points[3].x + pow_d (t, 3) * points[2].x;
double ty = pow_d(1-t, 3) * points[0].y + 3 * t * pow_d(1-t, 2) * points[1].y +
3 * pow_d(t, 2) * (1-t) * points[3].y + pow_d(t, 3) * points[2].y;
graphics_draw_pixel(ctx, GPoint(tx, ty));
}
}
```
**Notes:**
* `pow_d(double a, int b)` is simply a function which returns `b` to the power
of `a`.
* `points` contains array of 4 points used as parameters for drawing the Bezier
curve.
While this approach gives us precise results its also very computationally
expensive. Most of the work done by the CPU is redundant as most of those points
overlap each other due to the low screen resolution.
## Optimizing for Pebble
The simplest optimization would be reducing the number of steps and drawing
lines between computed points. While this will significantly reduce calculation
time, it will also make the curve less precise and pleasing to the eye. But what
if we could dynamically calculate the number of segments needed to create
perfectly smooth curve? Here's a method published by Maxim Shermanev which you
can find out more about on his website:
[www.antigrain.com](http://www.antigrain.com/research/adaptive_bezier/index.html).
Below you can see code from his research adapted to work efficiently on the
Pebble architecture:
```c
void recursive_bezier_fixed(int x1, int y1,
int x2, int y2,
int x3, int y3,
int x4, int y4){
// Calculate all the mid-points of the line segments
int x12 = (x1 + x2) / 2;
int y12 = (y1 + y2) / 2;
int x23 = (x2 + x3) / 2;
int y23 = (y2 + y3) / 2;
int x34 = (x3 + x4) / 2;
int y34 = (y3 + y4) / 2;
int x123 = (x12 + x23) / 2;
int y123 = (y12 + y23) / 2;
int x234 = (x23 + x34) / 2;
int y234 = (y23 + y34) / 2;
int x1234 = (x123 + x234) / 2;
int y1234 = (y123 + y234) / 2;
// Angle Condition
int32_t a23 = atan2_lookup((y3 - y2) / fixedpoint_base, (x3 - x2) / fixedpoint_base);
int32_t da1 = abs(a23 - atan2_lookup((y2 - y1) / fixedpoint_base, (x2 - x1) / fixedpoint_base));
int32_t da2 = abs(atan2_lookup((y4 - y3) / fixedpoint_base, (x4 - x3) / fixedpoint_base) - a23);
if(da1 >= TRIG_MAX_ANGLE) da1 = TRIG_MAX_ANGLE - da1;
if(da2 >= TRIG_MAX_ANGLE) da2 = TRIG_MAX_ANGLE - da2;
if(da1 + da2 < m_angle_tolerance)
{
// Finally we can stop the recursion
add_point(x1234 / fixedpoint_base, y1234 / fixedpoint_base);
return;
}
// Continue subdivision
recursive_bezier_fixed(x1, y1, x12, y12, x123, y123, x1234, y1234);
recursive_bezier_fixed(x1234, y1234, x234, y234, x34, y34, x4, y4);
}
bool bezier_fixed(GPathBuilder *builder, GPoint p1, GPoint p2, GPoint p3, GPoint p4) {
// Translate points to fixedpoint realms
int32_t x1 = p1.x * fixedpoint_base;
int32_t x2 = p2.x * fixedpoint_base;
int32_t x3 = p3.x * fixedpoint_base;
int32_t x4 = p4.x * fixedpoint_base;
int32_t y1 = p1.y * fixedpoint_base;
int32_t y2 = p2.y * fixedpoint_base;
int32_t y3 = p3.y * fixedpoint_base;
int32_t y4 = p4.y * fixedpoint_base;
if (recursive_bezier_fixed(builder, x1, y1, x2, y2, x3, y3, x4, y4)) {
return gpath_builder_line_to_point(builder, p4);
}
return false;
}
```
**Notes:**
* This code uses fixedpoint integers since the Pebble CPU doesn't support
floating point operations. You can find more about that in a talk given by
Matthew Hungerford during 2014 Pebble Developer Retreat (video available
[here](https://www.youtube.com/watch?v=8tOhdUXcSkw)).
* To determine the angle, the algorithm calculates parameters of the curve at
given points. This implementation is very effective since Pebble's
`atan2_lookup` is just looking up that value in a precomputed table.
* `m_angle_tolerance` is the angle of the curve we're looking for, expressed in
degrees. In our case it's 10 degrees: `int32_t m_angle_tolerance =
(TRIG_MAX_ANGLE / 360) * 10;`
## Applying Code to GPath
In order to make it easy for developers, we have prepared the GPathBuilder
library which will ease the process of creating GPaths out of a few Bezier
curves and/or lines. The resulting path can already be manipulated with the
[existing APIs](/docs/c/group___path_drawing.html#ga1ba79344b9a34432a44af09bed8b00fd). You can find it on the
[pebble-hacks Github page](https://github.com/pebble-hacks/gpath-bezier) along
with a simple demo app.
Usage is extremely simple. Here are the main functions in the API:
- `GPathBuilder* gpath_builder_create(uint32_t max_points)` will create the
GPathBuilder object you will need to use in order to create a GPath with
Bezier curves. `max_points` sets the limit on number of points created in the
process.
- `void gpath_builder_destroy(GPathBuilder *builder)` will destroy the
GPathBuilder object and free the memory it used.
- `bool gpath_builder_line_to_point(GPathBuilder *builder, GPoint to_point)`
will create a straight line from last point to the given point.
- `bool gpath_builder_curve_to_point(GPathBuilder *builder, GPoint to_point,
GPoint control_point_1, GPoint control_point_2)` will create a Bezier curve
from the last point to a given point (`to_point`), `control_point_1` and
`control_point_2` are used as parameters for the Bezier curve for last point
and given point respectively.
- `GPath* gpath_builder_create_path(GPathBuilder *builder)` will return a GPath
object ready to be used in your graphic routine. Remember that the
GPathBuilder is not being destroyed in the process and you have to do that
manually.
Below is shown a simple shape involving two curves and the code required to
create the path:
![result >{pebble-screenshot,pebble-screenshot--steel-black}](/images/blog/bezier-result.png)
```c
// Create GPathBuilder object
GPathBuilder *builder = gpath_builder_create(MAX_POINTS);
// Move to the starting point of the GPath
gpath_builder_move_to_point(builder, GPoint(0, -60));
// Create curve
gpath_builder_curve_to_point(builder, GPoint(60, 0), GPoint(35, -60), GPoint(60, -35));
// Create straight line
gpath_builder_line_to_point(builder, GPoint(-60, 0));
// Create another curve
gpath_builder_curve_to_point(builder, GPoint(0, 60), GPoint(-60, 35), GPoint(-35, 60));
// Create another straight line
gpath_builder_line_to_point(builder, GPoint(0, -60));
// Create GPath object out of our GPathBuilder object
s_path = gpath_builder_create_path(builder);
// Destroy GPathBuilder object
gpath_builder_destroy(builder);
// Get window bounds
GRect bounds = layer_get_bounds(window_get_root_layer(window));
// Move newly created GPath to the center of the screen
gpath_move_to(s_path, GPoint((int16_t)(bounds.size.w/2), (int16_t)(bounds.size.h/2)));
```
And there you have it, complex GPaths built with a few lines of code.
## Conclusion
This library should make it easier to create amazing graphics on Pebble and can
be used in animations as it's lightweight and fast. Bear in mind that the
GPathBuilder temporarily uses RAM proportinal to `max_points` until
`gpath_builder_destroy` is called. You can find the example app and library code
on the [pebble-hacks Github page](https://github.com/pebble-hacks/gpath-bezier).
We are also looking forward to this technology being used in online tools such
as [SVG to Pebble GPath Converter](http://gpathsvg.org/) by awesome Pebble
developer [Rajendra Serber](https://github.com/ardnejar).

View file

@ -0,0 +1,290 @@
---
# 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: Getting Started With Timeline
author: kirby
tags:
- Timeline
date: 2015-03-20
banner: /images/blog/getting-started-timeline.png
---
The new timeline interface is a completely new way to have users interact with
your app. The Pebble SDK along with timeline web APIs allows you to push pins to
your users' Pebbles. Adding pins to timeline is a straightforward process. This
guide will walk you through the steps from start to finish.
## The Project
We're going to build an app that lets users track their packages. We'll place pins
on the timeline for each package. For simplicity, we'll use the [Slice API](https://developer.slice.com/)
to get information about our packages. We'll go over the specifics of how to:
- Use the [PebbleKit JS timeline APIs](/guides/pebble-timeline/timeline-js/)
- Setup a server that utilizes the [pebble-api](https://www.npmjs.com/package/pebble-api)
npm module
- Enable timeline for the app through the [Developer Portal](https://dev-portal.getpebble.com)
## Setup
Before we dive into the timeline specifics of this app we'll first need to build
a typical Pebble app. I won't spend much time on how to build a basic Pebble app;
there are plenty of [guides](/guides/) for that.
In order to get setup with Slice follow their [Hello World example](https://developer.slice.com/docs/hello).
After you have your OAuth token it is easy to make a request to their
[shipments endpoint](https://developer.slice.com/docs/resources#res_shipments)
and get a JSON array of all your shipments. For example:
```bash
$ curl -X GET --header 'Authorization: XXXXXXXXXXXXXXXXXXXXXXXXXX' \
https://api.slice.com/api/v1/shipments
# response
{
"result": [
{
"status": {
"code": 3,
"description": "DELIVERED"
},
"updateTime": 1426785379000,
"shipper": {...},
"description": "Dockers Pants, Tapered Fit Alpha Khaki Flat Front Dark Pebble 34x32",
"receivedDate": "2015-03-20",
"receivedShare": null,
"trackingUrl": "https://tools.usps.com/go/TrackConfirmAction.action?tLabels=9261299998829554536102",
"shares": [...],
"merchantTrackingUrl": null,
"emails": [...],
"href": "https://api.slice.com/api/v1/shipments/2689846985907082329",
"shippingDate": "2015-03-17",
"items": [...],
"shippingEstimate": {...},
"trackingNumber": "9261299998829554536102",
"deliveryEstimate": {
"maxDate": "2015-03-20",
"minDate": "2015-03-20"
},
"destinationAddress": {...},
"history": [...]
}, ...
]
}
```
Our app will be pretty simple. We'll use ``AppSync`` to load a ``MenuLayer`` with
a list of packages that we get from Slice.
![package tracker app >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/package-tracker.png)
Here is the source code to [timeline-package-tracker]({{site.links.examples_org}}/timeline-package-tracker/tree/7edde5caa0f6439d2a0ae9d30be183ace630a147)
without any timeline support.
> Note that this is just an example. In a real app, you'd never hard code an
OAuth token into javascript, and you probably wouldn't arbitrarily limit your
app to have a 3 package limit.
## PebbleKit JS Timeline APIs
For our app we simply want to push each package as a pin to the timeline. Before
we can do this we need to get our user's timeline token. After we get the token
we'll need to send it to our own server. We'll also send our user's Slice oauth
token so that the server can keep up to date on all of their packages. From
there we can format a timeline pin with our package information and send it to
the user via their timeline token.
```js
var API_ROOT = 'YOUR_TIMELINE_SERVER';
var oauth = 'XXXXXXXXXXXXXXXXXXXX'; // in a real app we'd use a configuration window
Pebble.addEventListener('ready', function() {
doTimeline();
});
var doTimeline = function(packages) {
Pebble.getTimelineToken(function (token) {
sendToken(token, oauth);
}, function (error) {
console.log('Error getting timeline token: ' + error);
});
};
var sendToken = function(token, oauth) {
var request = new XMLHttpRequest();
request.open('GET', API_ROOT + '/senduserpin/' + token + '/' + oauth, true); // send the user's timeline token and Slice oauth token to our server
request.onload = function() {
console.log('senduserpin server response: ' + request.responseText);
};
request.send();
}
```
> Note that we're currently talking to a server that doesn't exist yet! We'll
cover how to set that up next.
## Pebble Timeline Web APIs
We'll need a server of our own to talk to the Pebble timeline web APIs.
[Express](https://github.com/strongloop/express/) is a convenient and easy way
to get a server up and running quickly. It also allows us to utilize the
[pebble-api](https://www.npmjs.com/package/pebble-api) npm module.
A basic Express server look like this:
```js
var express = require('express');
var request = require('request');
var Timeline = require('pebble-api');
var app = express();
app.set('port', (process.env.PORT || 5000));
var timeline = new Timeline();
app.get('/', function(req, res) {
res.send('Hello, world!');
});
// start the webserver
var server = app.listen(app.get('port'), function () {
console.log('Package Tracker server listening on port %s', app.get('port'));
});
```
We'll go ahead and include `request`, so that we can easily make requests to
Slice to get each user's package information. We'll also include the `pebble-api`
module too.
Now that we have a basic server up all we need to do is handle each request to
our server, fetch packages, and send pins. The `pebble-api` module will
make it super easy to create our pins according to
[spec](/guides/pebble-timeline/pin-structure/).
```js
var users = {}; // This is a cheap "in-memory" database ;) Use mongo db in real life!
app.get('/senduserpin/:userToken/:oauth', function(req, res) {
var userToken = req.params.userToken;
var oauth = req.params.oauth;
users[userToken] = {};
users[userToken]['oauth'] = oauth; // store user
users[userToken]['packages'] = [];
res.send('Success');
});
// every 5 minutes check for new packages for all users
setInterval(function() {
Object.keys(users).forEach(function(user) {
var userToken = user;
var oauth = users[user]['oauth'];
getPackages(oauth, userToken);
});
}, 300000);
var getPackages = function(oauth, userToken) {
var options = {
url: 'https://api.slice.com/api/v1/shipments',
headers: { 'Authorization': oauth }
};
request(options, function(error, response, body) {
var response = JSON.parse(body);
var pkgs = getCurrentPackages(response.result);
pkgs.forEach(function(pkg) {
var found = false;
users[userToken]['packages'].forEach(function(oldPkg) { // check if pkg is new or not
if(oldPkg.id === pkg.id) {
found = true;
}
});
if(!found) {
users[userToken]['packages'].push(pkg) // we have a new package, save it
sendPin(pkg, userToken); // and send it as a pin
}
});
});
};
// slice returns every package we've ever ordered. Let's just get the ones from the past week.
var getCurrentPackages = function(pkgs) {
current = [];
var oneWeekAgo = (new Date).getTime() - 604800000;
pkgs.forEach(function(pkg) {
if(pkg.updateTime > oneWeekAgo) {
current.push({'name': pkg.description, 'date': pkg.shippingEstimate.minDate, 'id': pkg.trackingNumber});
}
});
return current.slice(0, 3);
};
var sendPin = function(pkg, userToken) {
var pin = new Timeline.Pin({
id: pkg.id,
time: new Date(Date.parse(pkg.date) + 43200000), // will always be noon the day of delivery
layout: new Timeline.Pin.Layout({
type: Timeline.Pin.LayoutType.GENERIC_PIN,
tinyIcon: Timeline.Pin.Icon.MAIL,
title: pkg.name
})
});
timeline.sendUserPin(userToken, pin, function (err, body, resp) {
if(err) {
return console.error(err);
}
});
};
```
This will accept GET requests from our Pebble app, and will then store each user
in memory. From there it checks every 5 minutes for new packages, and if they exist
creates a pin for them. Since the carrier only tells us the date the package will
be delivered, we arbitrarily set the time of the pin to noon.
> Note in this current implementation users are stored in memory. A better
implementation would store packages in a database.
## Enable Timeline in the Developer Portal
We have almost everything setup, but our app won't work correctly with the
timeline web APIs until we upload the app's pbw to the Pebble Developer Portal
and enable timeline.
In order to enable timeline, perform the following steps:
0. Make sure you have a new and unique uuid. If you used the example from GitHub you will have to change it in your `appinfo.json`. To generate a new UUID, [you can use this tool](https://www.uuidgenerator.net/version4)
1. Login to the [Developer Portal](https://dev-portal.getpebble.com)
2. Click the Add Watchapp button on the portal (you don't need to upload any image assets)
3. Upload your pbw by adding a release (located in the build folder of your project)
4. Click the Enable Timeline button
And then you're done!
![package tracker pin >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/package-tracker-pin.png)
Checkout the full source code on [Github]({{site.links.examples_org}}/timeline-package-tracker/).
> **Further Reading on Pebble Timeline**
>
> For more information, we suggest you take a look at the [timeline guides](/guides/pebble-timeline/)
as well as the [hello-timeline]({{site.links.examples_org}}/hello-timeline)
and the [timeline-tv-tracker]({{site.links.examples_org}}/timeline-tv-tracker)
examples. They are all great resources to make the most of timeline!

View file

@ -0,0 +1,265 @@
---
# 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: The Road to Pebble SDK 3.0 in Ten Questions
author: thomas
tags:
- Freshly Baked
date: 2015-04-03
banner: /images/blog/road30-banner.png
---
We launched the first version of our new SDK with support for Pebble Time two
days into our Kickstarter campaign. Since then, we have updated it every week,
releasing six iterations of our developer preview. Some of these versions
included major new features like support for color in the emulator and support
for the timeline. Some other features were more subtle, like the new
antialiased drawing mode and the updates to the animation system. All these
changes together, and a bunch more that you have not seen yet, will form the
SDK that you will use to build watchfaces, watchapps and timeline apps for all
models of Pebble watches in the coming months.
These updates include lots of changes, and no matter how hard we try to
document everything there are a lot of unanswered questions. Some subjects have
just not been addressed by the developer previews yet, some need more
explaining and as always there are things we just missed in our communication
efforts.
In this update, I want to answer publicly the most frequent questions received
from the community in the last few weeks. I will cover components that have
been released but also those that are still to come so you have the information
needed to plan ahead and prepare your apps for the Pebble Time launch.
## What is the “timeline” shown in the Kickstarter video and how can I plug into it?
The timeline is a system-provided app that allows the user to navigate through
important events in their near future and their past.
![A glipse at the future (on the timeline) >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-timeline.gif)
Apps have a very important role to play in the timeline. All Pebble apps (but
not companion apps that use our native PebbleSDK on Android and iOS) can push
“pins” to the timeline. From a developer perspective, pins are just a block of
JSON which describes the information to show to the user and what layout to use
to display the information (generic, calendar, sports or weather). Pins can
also include notifications that will be shown to the user when the pin is
created or updated on the watch, and reminders that can trigger at a specific
time. Finally pins can have actions attached to them. Actions can open a
Pebble app with a `uint32` parameter.
You can experiment with pins in CloudPebbles new “Timeline” tab or with the
command line SDK and the `pebble insert-pin` command. For documentation, refer
to our [“Understanding Timeline Pins”](/guides/pebble-timeline/pin-structure/)
guide. Once you have nailed down what you want your pins to look and feel like
you can start pushing them via a public HTTP API. This API requires that your
app is submitted on the Pebble appstore; that is also how you get your API key
to use the timeline API. We have built a [Node.js
library](https://github.com/pebble/pebble-api-node) to make this easier and
David Moreau has already shared a
[PHP library](https://github.com/dav-m85/pebble-api-php/).
We also have a number of examples to help you get started:
* A very simple [timeline hello
world](https://github.com/pebble-examples/hello-timeline) that shows how
to push a pin every time you press a button in your app
* A [TV show tracker](https://github.com/pebble-examples/timeline-tv-tracker/)
that announces your favorite tv show and lets users click on the pins to
increase the number of viewers in realtime.
Get started now with our [Getting started with Timeline
post](/blog/2015/03/20/Getting-Started-With-Timeline/) or join us at the next
[Pebble SF developer meetup](http://www.meetup.com/PebbleSF/) on April 24th
where we will do a walkthrough of how to build a timeline app. If you are unable
to make it, you can also find this session on our [YouTube
channel](https://www.youtube.com/channel/UCFnawAsyEiux7oPWvGPJCJQ).
As a side note, many people have asked us what will happen if their app does
not integrate with the timeline. The new Pebble system still includes a main
menu in which all the apps will appear and they can be still be launched from
there.
## How can I build apps with transitions and animations like the ones shown in the Kickstarter videos?
The user interface in the new OS is full of extremely rich animations, and of
course we are not limiting them to the system applications. We want all apps to
take advantage of them and to achieve that, we are investing a lot of effort.
An essential part of all the animations is the core graphics framework used to
draw shapes, text and images. We have improved it a lot in recent weeks with
[much better support for animations](/guides/graphics-and-animations/animations),
[anti-aliasing and stroke width](/guides/graphics-and-animations/drawing-primitives-images-and-text/),
color bitmap handling and a lot of bugfixes and performance improvements.
Upcoming releases of the SDK will add the new UI components that you saw in the
Kickstarter video:
* A new MenuLayer with support for color, infinite scrolling (i.e. looping back
to the top), and new animations ![The new MenuLayer
>{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-menulayer.gif)
* A new ActionBar (available this week in developer preview 6!) that is wider,
takes the full height of the screen, and includes some new animations. ![The
updated ActionBar
>{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-actionbar.png)
* An ActionMenuWindow which is a full screen component to select an action in a
list. This is the same UI used to select an action on a pin or a
notification, and it is available to all apps. ![The new ActionMenuWindow
>{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-actionmenu.gif)
* A StatusBarLayer that gives you much more control over the status bar,
including the ability to animate it and add goodies like a progress indicator
and a card counter for the card pattern. ![The new StatusBar
>{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-statusbar.png)
* Cards are not really a UI Component but more of a standard pattern, seen in
the Weather app in the Kickstarter video and we expect that a lot more apps
will want to re-use it because it is an extremely efficient way to represent
information and looks very good! We will have a full example available to you
very soon showing how to build this type of app. ![A preview of the Weather
app and its animations
>{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/road30-card.gif)
For more information on the design of apps in SDK 3.0, we are putting the
finishing touches on a completely new design guide. Stay tuned!
## What is up with the icons on Pebble Time? Can I use these effects in my applications?
A lot of the new design aesthetic is provided by icons, and those icons are not
your usual bitmap file. They animate during transitions to give a level of life
and personality never seen in any User Interface framework before.
They are vector based animations and we came up with a complete new set of APIs
called Pebble Drawing Commands, as well as a new file format (.pdc) to
implement them. Compared to bitmap icons they can be automatically animated by
the system depending on the context: the same icon can explode on the screen,
regroup into a dot or fly through one of the screen edges; all of this in a
fraction of the space it would normally take to do such effects with animated
PNGs.
We will be providing a large set of icons that you can use in your timeline
pins and in your apps. You will also be able to make your own animated icons to
play in your app.
## What is really happening when I compile an app today to be compatible with all Pebbles?
With the current version of the Pebble SDK, we compile your apps once with SDK
2.9 for Aplite (Pebble and Pebble Steel) and once with SDK 3.0 for Basalt
(Pebble Time and Pebble Time Steel). This means that your apps can have a
different look on each platform. This is going to be even more true as we
introduce the new UI components in the next few releases.
![Aplite differences](/images/blog/road30-aplite-differences.png)
However, as soon as we provide official support for Aplite in SDK 3.0, we will
start compiling against the same set of APIs and the UI components will have
similar sizes, look and behavior.
All the new software features of Pebble Time will be available on the original
Pebble, including the timeline, as well as the new UI components, PNG, Pebble
Drawing Commands, unlimited apps, AppFaces, etc. Of course features that are
hardware-dependent (such as the microphone) will not be supported on Aplite.
We encourage everyone to maintain support for both Aplite and Basalt in their
source code. You can easily do it with `#ifdef` calls. If you just want to
freeze your Aplite app as it is today and focus on your Basalt app this will be
possible too with a new feature in the build process coming in the near future.
## What are AppFaces and when can we use them?
AppFaces are previews of your app that are displayed in the launcher before the
user starts your app. To use them, your app will need to support being launched
without a UI to update the AppFace.
![AppFaces](/images/blog/road30-appfaces.png)
AppFaces will not be part of Pebble SDK when we launch Pebble Time. Your apps
will just appear with their name until we add this API at a later stage. This
also means that your app icons defined in the appinfo.json are not used with
the new launcher (but they will still appear on Aplite until 3.0 ships for
Aplite).
## When can we start interacting with smartstraps?
We have released a lot of [mechanical and electrical information about
smartstraps](/smartstraps/) but no APIs yet. You will quickly find out that
without software support, the smartstrap does not get any power.
We are working hard with our first smartstrap partners to finalize the
specifications for this API. If you want to be a part of this discussion,
[please make yourself known!](/contact)
## How can I get my hands on Pebble Time? When will users get them?
If you did not order one on Kickstarter you can register on [our
website]({{ site.links.pebble }}/pebble_time/) to be notified as soon as they are
available for normal orders. We will ship every Kickstarter pledge before we
start selling them on the Pebble website. If you ordered one through the
Kickstarter campaign, we are working hard to get it to you as soon as possible.
As you know if you are a Kickstarter backer, Pebble Time starts shipping in May.
As a developer, you already have access to the emulator. It includes everything
you need to work on your app. If you are missing anything, [contact us](/contact).
With only about a month left, there is no time to lose!
## What about PebbleKit for iOS and Android? Any improvements?
We decided to focus all our efforts on the new UI framework and the timeline.
There are no functional changes to PebbleKit in SDK 3.0. However, all Android
apps must be recompiled with the new PebbleKit Android library to be compatible
with Pebble Time. If you do not recompile, your app will not work with Pebble
Time.
We have lots of plans for PebbleKit in the future and hope to share them
with you soon.
If you have suggestions for APIs youd like to see, please
[contact us](/contact)
## Will developers have access to the microphone? What can we do with it?
Absolutely. We will give developers access to our speech-to-text APIs. You will
be able to show a UI to start a recording and your app will receive the text
spoken by the user. This API is coming soon™ - not in 3.0.
We are considering other APIs such as direct microphone access. Stay tuned for
more information on those.
## Can we submit apps for Pebble Time to the Pebble appstore now?
No, you should not. As long as you are using a developer preview SDK, the APIs
are non-final and apps built with the developer preview SDK may not work with
the final firmware and SDK shipped with Pebble Time.
Once the APIs are stable, we will announce a final SDK and encourage everyone
to push applications built with the final SDK to the Pebble appstore.
## Want more answers?
For more questions and answers, such as the future of the `InverterLayer`,
information on floating point support or whether Pebble is really powered by
unicorns and rainbows, please take a look at this [/r/pebbledevelopers
thread](http://www.reddit.com/r/pebbledevelopers/comments/314uvg/what_would_you_like_to_know_about_pebble_sdk_30/).
Much thanks to all the developers who contributed questions to prepare this
blog post!
If there is anything else you would like to know, [this
thread](http://www.reddit.com/r/pebbledevelopers/comments/314uvg/what_would_you_like_to_know_about_pebble_sdk_30/)
is just [one]({{site.links.forums_developer}}) [of
the](https://twitter.com/pebbledev) [many
ways](/contact) [to get in touch](http://www.meetup.com/pro/pebble/)
with us and get answers!

View file

@ -0,0 +1,107 @@
---
# 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: Tips and Tricks - Transparent Images
author: chrislewis
tags:
- Beautiful Code
---
Ever wondered how to draw transparent images in a Pebble app? This post will
walk you through the process.
In this post, we'll be using a sample image with a transparency component, shown
below:
<a href="{{ site.asset_path }}/images/blog/tips-transparency-globe.png" download>
<img src="{{ site.asset_path }}/images/blog/tips-transparency-globe.png">
</a>
When adding your project resource, ensure you set its type correctly. On
CloudPebble, this is done when uploading the resource and choosing the 'PNG'
type. In the SDK, this is done with the `png` `type` in the project's
`appinfo.json`.
```js
"media": [
{
"type": "png",
"name": "GLOBE",
"file": "globe.png"
}
]
```
This will create a resource ID to use in code:
```text
RESOURCE_ID_GLOBE
```
Simply create a ``GBitmap`` with this resource and draw the image with
``GCompOpSet`` as the compositing mode:
```c
static GBitmap *s_bitmap;
static Layer *s_canvas_layer;
```
```c
static void window_load(Window *window) {
Layer *window_layer = window_get_root_layer(window);
// Create GBitmap
s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_GLOBE);
// Create canvas Layer
s_canvas_layer = layer_create(layer_get_bounds(window_layer));
layer_set_update_proc(s_canvas_layer, layer_update_proc);
layer_add_child(window_layer, s_canvas_layer);
}
```
```c
static void layer_update_proc(Layer *layer, GContext *ctx) {
// Draw the image with the correct compositing mode
graphics_context_set_compositing_mode(ctx, GCompOpSet);
graphics_draw_bitmap_in_rect(ctx, s_bitmap, gbitmap_get_bounds(s_bitmap));
}
```
When drawing on a ``TextLayer`` underneath `s_canvas_layer`, the result looks
like this:
![result-aplite >{pebble-screenshot,pebble-screenshot--steel-black}](/images/blog/tips-result-aplite.png)
See a full demo of this technique on the
[pebble-examples GitHub repo]({{site.links.examples_org}}/feature-image-transparent).
Job done! Any transparent pixels in the original image will be drawn as clear,
leaving the color beneath unaffected.
Read the [Image Resources](/guides/app-resources/) guide to learn more
about transparent PNGs.
## Conclusion
So there you have it. Using these examples you can easily implement transparency
on all Pebble platforms. To learn more, read the ``GCompOp`` documentation or
the
[`pebble-examples/feature-image-transparent`]({{site.links.examples_org}}/feature-image-transparent)
SDK example.

View file

@ -0,0 +1,226 @@
---
# 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: Tips and Tricks - Platform-specific C File Set
author: chrislewis
tags:
- Beautiful Code
---
In
[the last Tips and Tricks blog post](/blog/2015/05/13/tips-and-tricks-transparent-images/)
we looked at drawing transparent images on both the Aplite and Basalt platforms.
This time around, we will look at a `wscript` modification that can allow you to
build a Pebble project when you have two completely separate sets of C source
files; one set for Aplite, and another for Basalt.
Note: This technique can be applied only to local SDK projects, where access to
`wscript` is available. This means that it cannot be used in CloudPebble
projects.
> Update 05/20/15: There was an error in the JS code sample given at the end of
> this post, which has now been corrected.
## The Preprocessor Approach
In the [3.0 Migration Guide](/sdk/migration-guide/#backwards-compatibility) we
recommend using preprocessor directives such as `PBL_PLATFORM_APLITE` and
`PBL_PLATFORM_BASALT` to mark code to be compiled only on that particular
platform. This helps avoid the need to maintain two separate projects for one
app, which is especially convenient when migrating a 2.x app to the Basalt
platform.
```c
#ifdef PBL_PLATFORM_APLITE
// Aligns under the status bar
layer_set_frame(s_layer, GRect(0, 0, 144, 68));
#elif PBL_PLATFORM_BASALT
// Preserve alignment to status bar on Aplite
layer_set_frame(s_layer, GRect(0, STATUS_BAR_LAYER_HEIGHT, 144, 68));
#endif
```
This is a good solution for small blocks of conditional code, but some
developers may find that complicated conditional code can soon become more
`#ifdef...#elif...#endif` than actual code itself!
## The Modified Wscript Approach
In these situations you may find it preferable to use a different approach.
Instead of modifying your app code to use preprocessor statements whenever a
platform-specific value is needed, you can modify your project's `wscript` file
to limit each compilation pass to a certain folder of source files.
By default, you will probably have a project with this file structure:
```text
my_project
resources
images
banner~bw.png
banner~color.png
src
main.c
util.h
util.c
appinfo.json
wscript
```
In this scenario, the `wscript` dictates that *any* `.c` files found in `src`
will be compiled for both platforms.
To use a different set of source files for each platform during compilation,
modify the lines with the `**` wildcard (within the `for` loop) to point to a
folder within `src` where the platform- specific files are then located:
```python
for p in ctx.env.TARGET_PLATFORMS:
ctx.set_env(ctx.all_envs[p])
ctx.set_group(ctx.env.PLATFORM_NAME)
app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR)
# MODIFY THIS LINE!
# E.g.: When 'p' == 'aplite', look in 'src/aplite/'
ctx.pbl_program(source=ctx.path.ant_glob('src/{}/**/*.c'.format(p)), target=app_elf)
if build_worker:
worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR)
binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
# MODIFY THIS LINE!
# Also modify this line to look for platform-specific C files in `worker_src`
ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/{}/**/*.c'.format(p)), target=worker_elf)
else:
binaries.append({'platform': p, 'app_elf': app_elf})
```
With this newly modified `wscript`, we must re-organise our `src` folder to
match the new search pattern. This allows us to maintain two separate sets of
source files, each free of any excessive `#ifdef` pollution.
```text
my_project
resources
images
banner~bw.png
banner~color.png
src
aplite
main.c
util.h
util.c
basalt
main.c
util.h
util.c
appinfo.json
wscript
```
## Sharing Files Between Platforms
Using the modified wscript approach as shown above still requires any files that
are used on both platforms to be included twice: in the respective folder. You
may wish to reduce this clutter by moving any platform-agnostic files that both
platforms use to a `common` folder inside `src`.
You project might now look like this:
```text
my_project
resources
images
banner~bw.png
banner~color.png
src
aplite
main.c
basalt
main.c
common
util.h
util.c
appinfo.json
wscript
```
To tell the SDK to look in this extra folder during compilation of each
platform, further modify the two lines calling `ctx.pbl_program()` to include
the `common` folder in the array of paths passed to
[`ant_glob()`](https://waf.io/book/#_general_usage). This is shown in the code
snipped below, with unchanged lines ommitted for brevity:
```python
# Additionally modified to include the 'common' folder
ctx.pbl_program(source=ctx.path.ant_glob(['src/{}/**/*.c'.format(p), 'src/common/**/*.c']), target=app_elf)
/* Other code */
if build_worker:
# Also modify this line to look for common files in '/worker_src/common/'
ctx.pbl_worker(source=ctx.path.ant_glob(['worker_src/{}/**/*.c'.format(p), 'worker_src/common/**/*.c']), target=worker_elf)
else:
/* Other code */
```
## Important Notes
While this new `wscript` allows us to keep our source files for each platform
separated entirely, there are a couple of important limitations to take into
account when using this method (without any further modification):
* There can still be only *one* JavaScript file, in `src/js/pebble-js-app.js`.
You can simulate two JS files using a platform check:
```js
Pebble.addEventListener('ready', function() {
if(Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo().platform === 'basalt') {
// This is the Basalt platform
console.log('PebbleKit JS ready on Basalt!');
} else {
// This is the Aplite platform
console.log('PebbleKit JS ready on Aplite!');
}
});
```
* Each binary can be bundled with only the app resources required for that
specific platform. To learn how to package app resources with only a certain
platform, read the
[*Platform-specific Resources*](/guides/app-resources/platform-specific/)
guide.
## Conclusion
With this modification to a Pebble project's `wscript`, developers now have two
options when it comes to diverging their app code for Aplite- and Basalt-
specific features **without** the need to maintain two completely separate
projects.
You can see a simple example project that ses all these techniques over at
[`pebble-examples/multi-platform-wscript`]({{site.links.examples_org}}/multi-platform-wscript).

View file

@ -0,0 +1,99 @@
---
# 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 to Pebblekit iOS 3.0
author: alex
tags:
- Freshly Baked
---
Starting with Pebble Time Round, we are moving towards communicating with Bluetooth
Low-Energy only. This means there are some updates to PebbleKit iOS to support this. Here are
the major changes and steps to take to support the new BLE connection.
**What's New**
* Companion apps have dedicated, persistent communication channels
* Start mobile apps from Pebble
* 8K AppMessage buffers
* Swift support
## What do you need to do?
Import the new [PebbleKit iOS 3.0](/guides/migration/pebblekit-ios-3/#how-to-upgrade) library into your project.
### API Changes
#### NSUUIDs
You can now use `NSUUID` objects directly rather than passing ``appUUID`` as a `NSData` object.
**PebbleKit 2.x**
```c
uuid_t myAppUUIDbytes;
NSUUID *myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"];
[myAppUUID getUUIDBytes:myAppUUIDbytes];
[PBPebbleCentral defaultCentral].appUUID = [NSData dataWithBytes:myAppUUIDbytes length:16];
```
**PebbleKit 3.0**
```c
NSUUID *myAppUUID = [[NSUUID alloc] initWithUUIDString:@"226834ae-786e-4302-a52f-6e7efc9f990b"];
[PBPebbleCentral defaultCentral].appUUID = myAppUUID;
```
#### Cold start PBPebbleCentral
You'll want to start ``PBPebbleCentral`` in a cold state now so users don't get
a pop-up asking for Bluetooth permissions as soon as the app initializes. Call
`[central run]` when it makes sense for the pop-up to show up. Add some custom
text to the dialog with `NSBluetoothPeripheralUsageDescription` to your
`Info.plist` file.
**PebbleKit 2.x**
```c
// MyAppDelegate.m - Set up PBPebbleCentral and run if the user has already
// performed onboarding
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[PBPebbleCentral defaultCentral].delegate = self;
[PBPebbleCentral defaultCentral].appUUID = myAppUUID;
if ([MySettings sharedSettings].userDidPerformOnboarding) {
[[PBPebbleCentral defaultCentral] run];
}
}
```
**PebbleKit 3.0**
```c
// MyOnboarding.m - Once the pop-up has been accepted, begin PBPebbleCentral
- (IBAction)didTapGrantBluetoothPermissionButton:(id)sender {
[MySettings sharedSettings].userDidPerformOnboarding = YES;
[[PBPebbleCentral defaultCentral] run]; // will trigger pop-up
}
```
### Specify that your app is built with PebbleKit 3.0 in the Developer Portal
Go to edit the companion app listing in your [developer portal](https://dev-portal.getpebble.com/developer) page and check the box for "Was this iOS app compiled with PebbleKit iOS 3.0 or newer?" This way, users on Pebble Time Round will be able to see your app in the appstore.
![](/images/blog/checkbox.png)
### Final thoughts
With a few quick steps, you can bring compatability for the BLE connection to your app. In the coming months, we'll be rolling out updates for users of Pebble and Pebble Time to take advantage of BLE-only connection as well. In the short term, if you intend to support Pebble Time Round, these steps are mandatory. For the complete details on migrating to PebbleKit 3.0, take a look at our [migration guide](/guides/migration/pebblekit-ios-3/). If you have any issues in migrating or have any questions concerning PebbleKit 3.0, feel free to [contact](/contact/) us anytime!

View file

@ -0,0 +1,192 @@
---
# 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: Nuance Brings Pebble The Freedom Of Speech
author: jonb
tags:
- Freshly Baked
---
In October, we gave developers access to the microphone in the Pebble Time via
our new
[Dictation API](``Dictation``),
and almost instantly we began seeing awesome projects utilising speech input.
Voice recognition is an exciting new method of interaction for Pebble and it has
created an opportunity for developers to enhance their existing applications, or
create highly engaging new applications designed around the spoken word.
Speech-to-text has been integrated with Pebble by using the
[Recognizer](http://www.nuance.com/for-business/automatic-speech-recognition/automated-ivr/index.htm)
cloud service from Nuance, a leading provider of voice and language solutions.
## The Pebble Dictation Process
The Dictation API has been made incredibly easy for developers to integrate into
their watchapps. Its also intuitive and simple for users to interact with.
Heres an overview of the the dictation process:
1. The user begins by pressing a designated button or by triggering an event
within the watchapp to indicate they want to start dictating. The watchapps
UI should make this obvious.
2. The watchapp
[initiates a dictation session](/docs/c/Foundation/Dictation/#dictation_session_start),
assigning a
[callback function](/docs/c/Foundation/Dictation/#DictationSessionStatusCallback)
to handle the response from the system. This response will be either
successful and return the dictated string, or fail with an error code.
3. The system Dictation UI appears and guides the user through recording their
voice. The stages in the image below illustrate:
a. The system prepares its buffers and checks connectivity with the cloud
service.
b. The system begins listening for speech and automatically stops listening
when the user finishes talking.
c. The audio is compressed and sent to the cloud service via the mobile
application.
d. The audio is transcribed by the cloud service and the transcribed text is
returned and displayed for the user to accept or reject (this behaviour
can be
[programmatically overridden](/docs/c/Foundation/Dictation/#dictation_session_enable_confirmation)).
4. Once the process has completed, the registered callback method is fired and
the watchapp can deal with the response.
![dictation-flow](/images/blog/dictation-flow.png)
## But How Does It Actually Work?
Lets take a closer look at whats happening behind the scenes to see whats
really going on.
![dictation-recognizer](/images/blog/dictation-recognizer.png)
1. To capture audio, Pebble Time (including Time Steel and Time Round) has a
single [MEMS](https://en.wikipedia.org/wiki/Microelectromechanical_systems)
microphone. This device produces output at 1 MHz in a
[PDM](https://en.wikipedia.org/wiki/Pulse-density_modulation) format.
2. This 1 bit PDM signal needs to be converted into 16-bit
[PCM](https://en.wikipedia.org/wiki/Pulse-code_modulation) data at 16 kHz
before it can be compressed.
3. Compression is performed using the [Speex](http://www.speex.org/) encoder,
which was specifically designed for speech compression. Compression needs to
occur in order to reduce the overall size of the data before its transferred
via bluetooth to the mobile application. Speex also has some additional
advantages like tuneable quality/compression and recovery from dropped
frames.
4. The mobile application sends the compressed data to Nuance Recognizer, along
with some additional information like the users selected language.
5. Nuance performs its magic and returns the textual representation of the
spoken phrase to the mobile application, which is then automatically passed
back to the watchapp.
6. The Dictation UI presents the transcribed text back to the user where they
can choose to accept or reject it.
## About the Dictation API
Behind the scenes theres a lot going on, but lets take a look at how minimal
the code needs to be in order to use the API.
1. Create a static variable as a reference to the dictation session:
```c
static DictationSession *s_dictation_session;
```
2. Create a callback function to receive the dictation response:
```c
static void dictation_session_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) {
if(status == DictationSessionStatusSuccess) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Transcription:\n\n%s", transcription);
} else {
APP_LOG(APP_LOG_LEVEL_DEBUG, "Transcription failed.\n\nError ID:\n%d", (int)status);
}
}
```
3. Within a button click or other event, create a ``DictationSession`` to begin
the process:
```c
s_dictation_session = dictation_session_create(512, dictation_session_callback, NULL);
```
4. Before your app exits, dont forget to destroy the session:
```c
dictation_session_destroy(s_dictation_session);
```
## Voice-enabled Watchapps
Heres a small sample of some of the watchapps which are already available in
the Pebble appstore which utilise the Dictation API.
* [Voice2Timeline](http://apps.getpebble.com/en_US/application/561f9188bcb7ac903a00005b)
is a handy tool for quickly creating pins on your timeline by using your
voice. It already works in 6 different languages. You can leave notes in the
past, or even create reminders for the future (e.g. “Dont forget the milk in
1 hour”).
* [Translate (Vox Populi)](http://apps.getpebble.com/en_US/application/561ff3cbbcb7aca6250000a3)
allows a user to translate short phrases and words into a different language.
It uses the [Yandex](https://translate.yandex.com/) machine translator API
which supports more than 60 different languages.
* [Checklist](http://apps.getpebble.com/en_US/application/5620e876768e7ada4e00007a)
is a really simple tool which generates a list of items using your voice. It
even allows you to enter multiple items at once, by specifying a comma or
period. You can easily mark them as completed by pressing the select button
on each item.
* [Smartwatch Pro](https://itunes.apple.com/gb/app/smartwatch-pro-for-pebble/id673907094?mt=8)
(iOS) has been updated to give users voice controlled music playback, create
reminders and even create tweets by using their voice.
## Final Thoughts
Why not also checkout this video of Andrew Stapleton (Embedded Developer) as he
deep dives into the internals of the Pebble Dictation API during his presentation
at the [Pebble Developer Retreat 2015](/community/events/developer-retreat-2015/).
[EMBED](www.youtube.com/embed/D-8Ng24RXwo)
We hope youve seen how flexible and easy it is to use the new Dictation API,
and perhaps it will inspire you to integrate voice into your own application or
watchface - if you create a voice enabled watchapp, let us know by tweeting
[@pebbledev](https://twitter.com/pebbledev).
If youre looking to find out more about voice integration, checkout our
[developer guide](/guides/events-and-services/dictation/),
[API documentation](``Dictation``)
and our
[simple example app](https://github.com/pebble-examples/simple-voice-demo). We
also have a very friendly and helpful Pebble community on Discord; why not
[join us]({{ site.links.discord_invite }})?

View file

@ -0,0 +1,117 @@
---
# 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: Introducing Pebble Tool 4.0
author: katharine
tags:
- Freshly Baked
---
I am pleased to today announce that version 4.0-rc4 of the `pebble` tool is now
available. The key new feature is a new paradigm for dealing with firmware and
SDK versions. This makes it much easier to deal with differing SDK versions, or
to test code on multiple (emulated) firmware versions.
_A note: while the tool is now at version 4.0, the SDK, firmware and mobile apps
will not be following. Pebble tool versioning is now completely independent of
the rest of the Pebble ecosystem._
Managing SDKs
------------
The pebble tool now manages SDKs for you, without you needing to download and
install the entire SDK manually each time. The first time you need an SDK, the
latest one will be automatically installed for you. After that, you can use the
SDK operations that live under the `pebble sdk` subcommand.
To see a list of available SDKs, use `pebble sdk list`:
```nc|text
katharine@kbrmbp ~> pebble sdk list
Installed SDKs:
3.7 (active)
Available SDKs:
3.6.2
3.4
3.3
3.2.1
3.1
3.0
2.9
```
You can install any SDK using `pebble sdk install`, like so:
```nc|text
katharine@kbrmbp ~> pebble sdk install 3.6.2
Installing SDK...
Do you accept the Pebble Terms of Use and the Pebble Developer License? (y/n) y
Downloading...
100%[======================================================] 1.40 MB/s 0:00:01
Extracting...
Preparing virtualenv... (this may take a while)
Installing dependencies...
Done.
Installed.
```
You can switch between active SDKs using `pebble sdk activate <version>`, like
`pebble sdk activate 3.7`. Once you activate an SDK, it will be used for all
`build` and `install` commands.
Switching on the fly
--------------------
A number of commands now take an optional `--sdk` flag, which will override the
current active SDK. This enables you to easily run one command with a different
SDK version — for instance, compiling with 3.6.2 and then running on 3.7:
```nc|text
katharine@kbrmbp ~> pebble build --sdk 3.6.2
# ...
katharine@kbrmbp ~> pebble install --emulator basalt --sdk 3.7
# ...
```
This is supported by `pebble build` as well as any command that supports
`--emulator`. Additionally, you can now run emulators for multiple SDKs
simultaneously by passing different values for `--sdk`.
Benefits
--------
Beyond the obvious benefit of easier SDK management, the new system also
produces much smaller SDKs. Each SDK used to be a 38 MB download, which
decompressed to 143 MB, plus another hundred megabytes for the toolchain.
Most of this is now downloaded only once, as part of the initial pebble tool
setup. After that, each SDK is only a 2 MB download, which expands to 4 MB on
disk.
The new pebble tool can also alert you to new SDKs as they become available,
enabling you to install them with a single command.
Try it out!
-----------
To try out our new pebble tool, read the instructions on the [SDK
Beta](/sdk/beta) page.
Please [contact us](/contact/) if you run into any issues installing or using
`pebble` v4.0, or if you have any feedback. You can also frequently find me
on ~~Slack~~ Discord — [join us]({{ site.links.discord_invite }})!

View file

@ -0,0 +1,163 @@
---
# 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: Unifying bitmap resources
author: katharine
tags:
- Freshly Baked
---
With the upcoming release of firmware 3.8 on Pebble and Pebble Steel, and the
associated SDK 3.8, we have decided to redesign how image resources work in
Pebble apps.
Why change?
-----------
First, some history: when SDK 1 was originally released, and continuing up to
SDK 2.9, there was a single image type, `png`. A `png` resource would take a PNG
as input and spit out a custom, uncompressed, 1-bit-per-pixel image format we
called "pbi". This was the only bitmap format Pebble supported, and life was
simple.
With the release of SDK 3.0, we added firmware support for a new image format:
PNG (with some restrictions). This enabled Pebble to directly read compressed
images, and those images could be 1-bit, 2-bit, 4-bit or 8-bit palettised. The
existing `png` resource type was changed to produce these images instead of the
old PBI format, and everyone had smaller image resources.
Unfortunately, "png" isn't the best option for all cases. The old 1-bit format
supported some [compositing operations](``GCompOp``) that other image formats
do not support. We added the `pbi` format to achieve this legacy behavior.
Additionally, PNG decompression isn't free: loading a PNG resource requires
enough memory to hold the compressed image, the uncompressed image, and some
scratch space. This was often offset by the benefits of palettised images with
fewer bits per pixel, but sometimes it didn't fit. We added `pbi8`, which
produced 8-bit-per-pixel PBI images. This still left it impossible to generate
a palettized PBI, even though the format does exist.
As a further complication, since SDK 1 and continuing through the present day,
`pbi` (and `pbi8`) images have cropped transparent borders around the outside.
However, `png` images (as of SDK 3) do _not_ crop like this. The cropping
behavior was originally a bug, and is generally undesirable, but must be
maintained for backwards compatibility.
There is one additional exception to all of this: until SDK 3.8, the Aplite platform still
interprets `png` to mean `pbi`. It also interprets `pbi8` to mean `pbi`. When
we built the 3.8 SDK, we changed `png` to really mean `png` on Aplite.
Unfortunately, the more limited memory of the Aplite platform meant that these
PNGs sometimes did not have enough space to decompress. The only workaround was
to duplicate resources and use `targetPlatforms` to specify a `pbi` resource for
Aplite and a `png` resource for Basalt and Chalk.
The easiest answer was to keep `png` as an alias for `pbi` on Aplite—but then
there's no way of generating a real PNG for Aplite. Furthermore, the `png`,
`pbi` and `pbi8` trio was getting confusing, so we decided to do something else.
"bitmap" to the rescue
----------------------
As of SDK 3.8, `png`, `pbi` and `pbi8` **are all deprecated**. We are instead
introducing a new resource type, `bitmap`. This new resource type unifies all
the existing types, allows the SDK to use its best judgement, increases the
flexibility available to developers, and takes the guesswork out of advanced
image manipulation. It also removes the generally undesirable forced cropping
behavior.
The simplest option is to only specify that you want a `bitmap` resource, and
by default the SDK will do the most reasonable thing:
```js
{
"type": "bitmap",
"name": "IMAGE_BERRY_PUNCH",
"file": "images/berry-punch.png"
}
```
This will create an image with the smallest possible in-memory representation,
which will depend on the number of colors. On Aplite, where memory is tight,
it will optimize for low memory consumption by creating a pbi. On all other
platforms, where there is more memory to spare, it will create a png.
This behavior can be overridden using the following attributes:
* `memoryFormat`: This determines the `GBitmapFormat` that the resulting image
will have. The default value is `Smallest`, which picks the value that will
use the least memory. If you have specific requirements, you can specify:
`SmallestPalette`, `1BitPalette`, `2BitPalette`, `4BitPalette`, `1Bit` or
`8Bit`. If an image cannot be represented in the requested format, a build
error will result. This, for instance, enables palette manipulation with
static checking for confidence that you will have an appropriate palette to
manipulate.
* `spaceOptimization`: This determines whether we should optimize the image for
resource space (`storage`) or memory (`memory`). The default depends on the
memory available on the platform: Aplite defaults to `memory`, while other
platforms default to `storage`.
* `storageFormat`: This explicitly states whether an image should be stored on
flash as a PNG or pbi image. In most cases you should not specify this, and
instead depend on `spaceOptimization`. However, if you read resources
directly, this option exists to force a value.
So if you want an image that will always have a 2-bit palette and use as little
memory as possible, you can do this:
```js
{
"type": "bitmap",
"name": "IMAGE_TIME_TURNER",
"file": "images/time-turner.png",
"memoryFormat": "2BitPalette",
"spaceOptimization": "memory"
}
```
The resulting image will always have `GBitmapFormat2BitPalette`, even if it
could have a 1-bit palette. If it has more than four colors, the build will
fail.
In SDK 3.8-beta8, this would result in a 2-bit palettized pbi. However, this
is not guaranteed: we can change the format in the future, as long as the result
has ``GBitmapFormat2BitPalette`` and we prefer to optimise for memory
consumption where possible.
Finally, note that some combinations are impossible: for instance, a `1Bit`
image can never be stored as a PNG, so `"storageFormat": "png"` combined with
`"memoryFormat": "1Bit"` will be a compile error.
Migration
---------
If you have an existing app, how do you migrate it to use the new `bitmap` type?
If you were depending on the `pbi` cropping behavior, you will have to manually
crop your image. Beyond that, this table gives the equivalences:
| SDK 3.7 type | `bitmap` specification |
|--------------|----------------------------------------------|
| `png` | `{"type": "bitmap"}` |
| `pbi` | `{"type": "bitmap", "memoryFormat": "1Bit"}` |
| `pbi8` | `{"type": "bitmap", "storageFormat": "pbi"}` |
`png-trans` has been left out of this entire exercise. Its behavior is unchanged:
it will produce two pbis with `GBitmapFormat1Bit`. However, `png-trans` is also
deprecated and discouraged. As of SDK 3.8, all platforms support transparency
in images, and so should use `bitmap` instead.
If you have any questions, you can [find us on Discord]({{ site.links.discord_invite }})
or [contact us](/contact/).

View file

@ -0,0 +1,147 @@
---
# 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: Bringing the Family Back Together - 3.x on Aplite is Almost Here!
author: chrislewis
tags:
- Freshly Baked
banner: /images/blog/3.x-on-tintin.png
---
The time has almost come for Pebble Classic and Pebble Steel to get firmware
3.x!
This is great news for users who get access to all the improvements and fixes
since 2.x, as well as new features such as timeline, Quiet Time, no more eight
app limit, Standby Mode, and last but not least one new color (gray)!
But let us not forget about Basalt and Chalk app developers, who also have a lot
to get excited about - a huge new user base of Aplite watch wearers!
This blog post aims to help developers using SDK 2.x features and APIs to
migrate their apps to a single 3.x codebase. As well as bringing compatibility
to all older watches when they upgrade to firmware 3.x, these changes will also
serve to remove a lot of complex and ugly conditional code that has needed to
exist in the meantime. Let's fix some apps!
## Get the Beta SDK
To try out the beta SDK, read the instructions on the [SDK Beta](/sdk/beta)
page.
## Mandatory Changes
To be compatible with users who update their Pebble Classic or Pebble Steel to
firmware 3.x the following important changes **MUST** be made:
* If you are adding support for Aplite, add `aplite` to your `targetPlatforms`
array in `appinfo.json`, or tick the 'Build Aplite' box in 'Settings' on
CloudPebble.
* Recompile your app with at least Pebble SDK 3.8 (coming soon!). The 3.x on
Aplite files will reside in `/aplite/` instead of the `.pbw` root folder.
Frankenpbws are **not** encouraged - a 2.x compatible release can be uploaded
separately (see [*Appstore Changes*](#appstore-changes)).
* Update any old practices such as direct struct member access. An example is
shown below:
```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);
```
* If your app uses either the ``Dictation`` or ``Smartstrap`` APIs, you must
check that any code dependent on these hardware features fails gracefully when
they are not available. This should be done by checking for `NULL` or
appropriate `enum` values returned from affected API calls. An example is
shown below:
```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 doesnt unpublish the previous one. You can still manually unpublish
releases whenever they want.
* The appstore will provide the most recently compatible release of an app to
users. This means that if you publish a new release that has 3.x Aplite
support, the newest published release that supports 2.x Aplite will be
provided to users on 2.x Aplite.
* There will be a fourth Asset Collection type that you can create: Legacy
Aplite. Apps that have different UI designs between 2.x and 3.x on Aplite
should use the Legacy Aplite asset collection for their 2.x assets.
## Suggested Changes
To fully migrate to SDK 3.x, we also suggest you make these non-essential
changes:
* Remove any code conditionally compiled with `PBL_SDK_2` defines. It will no
longer be compiled at all.
* Ensure that any use of ``app_message_inbox_size_maximum()`` 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, gray, or white). We recommend checking
every instance of a `GColor`to ensure each is the correct one.
* In addition to the point above, investigate how the contrast and readability
of your app can be improved by making use of gray (either `GColorLightGray` or
`GColorDarkGray`). Examples of this can be seen in the system UI in the banner
at the top of this blog post.
* Apps using image resources should take advantage of the new `bitmap` resource
type, which optimizes image files for you. Read the
[*Unifying Bitmap Resources*](/blog/2015/12/02/Bitmap-Resources/)
blog post to learn more.
## Questions?
That's it! It will be quite straightforward to update most of your apps, but if
you do have any problems or queries, feel free to
[contact us](/contact/) or find us on [Discord]({{ site.links.discord_invite }}).

View file

@ -0,0 +1,127 @@
---
# 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: Multiple JavaScript Files
author: katharine
tags:
- Freshly Baked
---
In SDK 3.9 we will introduce a new feature to the Pebble SDK: the ability to
cleanly use multiple JavaScript files in the PebbleKit JS app portion of
your project.
<strike>SDK 3.9 is available now in beta! Check out our
[beta instructions](/sdk/download/#testing-beta-sdks) to try it out.</strike>
**EDIT:** SDK 3.9 is now [publicly available](/sdk/) - to update your SDK, run: ```pebble sdk install latest```
# How do I use this?
First, if you are using the native SDK, you must make sure you have
`"enableMultiJS": true` in your appinfo.json file. This defaults to false if
omitted, but will be set to `true` in all projects created using SDK 3.9+ and
Pebble Tool 4.1+. If you are using CloudPebble, ensure that "JS Handling" is
set to "CommonJS-style" in your project's settings page.
Having enabled it, PebbleKit JS now expects to have an "entry point" at
`src/pkjs/index.js`. This is the code we will run when your app starts, and is
equivalent to the native SDK's old `src/js/pebble-js-app.js`. Aside from a
slightly less redundant name, the effective behavior is exactly the same as
before.
However, you can now add additional javascript files! These files will not be
executed automatically. Instead, you can pull them in to your code using the
`require` function, which returns a reference to the imported file.
Since `require` returns something, there needs to be some way to provide
something that it can usefully return. To do this, add properties to
`module.exports` in the file to be required. For instance:
**adder.js**
```js
function addThings(a, b) {
return a + b;
}
module.exports.addThings = addThings;
```
**index.js**
```js
var adder = require('./adder');
console.log("2 plus 2 is " + adder.addThings(2, 2));
```
Running the app with this JavaScript would look like this:
```nc|text
katharine@scootaloo ~> pebble install --emulator basalt --logs
Installing app...
App install succeeded.
[17:39:40] javascript> 2 plus 2 is 4
```
That's about all there is to it: we handle this all transparently.
# But…!?
### …how do I migrate my existing project?
If you use the native SDK and your existing project has only one JavaScript
file, just move it from `src/js/pebble-js-app.js` to `src/pkjs/index.js` and add
`"enableMultiJS": true` to your appinfo.json file. Easy!
If you use CloudPebble and have multiple JavaScript files, you first change
"JS Handling" to "CommonJS-style" in your project's Settings page. If you don't
have a JavaScript file called "index.js", but you do have some others, it will
prompt you to rename a file. If you previously used multiple JavaScript files
on CloudPebble, you will now need to make code changes. In particular,
you will have to modify your code so that there is a single, clearly-defined
entry point (`index.js`) and the other files use the module export system
described above.
That said, there is no need to do this now: the existing system will continue to
work for the forseeable future.
### …will this work for users who haven't updated their mobile apps?
Yes! This all happens entirely at build time; no updates to the user's phone
software are required. Since it is part of SDK 3.9, they will need to be running
firmware 3.9, as usual.
### …I use Pebble.js. What does this mean for me?
Nothing! Pebble.js already does something very similar to this, and it will
continue to do so. No changes are needed.
### …I built something like this myself. Do I have to use this?
No! If you want to keep using your own custom system, you can set
`enableMultiJS` to false or omit it entirely, and nothing will change. The
system is strictly opt-in, and won't break anything you were doing if you
leave it as-is.
### …I don't want my main JavaScript file to be called "index.js"
We recommend that you use the default name for portability reasons. In
particular, CloudPebble will _require_ that you use this name, and will fail
to build a non-compliant project. However, if you would really like to change
it, you can pass an alternate path as `js_entry_file` to `pbl_bundle` in your
wscript file.

View file

@ -0,0 +1,248 @@
---
# 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: JavaScript Libraries for C Developers (pt. 1)
author: cat
tags:
- Freshly Baked
- Beautiful Code
---
One of the exciting changes introduced in [SDK 3.9] [sdk 3.9] was support for
including [Multiple JavaScript Files] [js blog] to your projects. While this
feature doesnt allow you to run any JavaScript you previously couldnt, it
makes organizing your PebbleKit JS code a heck of a lot easier.
In this blog post, we'll look at how to refactor some exisiting JavaScript
'library code' into an actual module for PebbleKit JS, making it simpler to use
and share!
## OWM-Weather
A few months ago we published a very simple 'library' for working with the
[Open Weather Map] [owm] API. The JavaScript side of the code takes care of
grabbing the users location with the built in [geolocation.getCurrentPosition]
[getcurrentposition] API, then fetches weather information for the returned
position.
The initial version of the code were working with can be found [here] [owm lib 0].
## Creating JavaScript Classes
> **Note:** While JavaScript (with ECMAS5) does not allow for true "Classes",
> JavaScript does a support a mechanism that allows you write Class-like code.
> For convience, we'll be refering to these objects as 'classes' throughout
> this post.
The first thing we want to do is wrap our library code in an Object constructor
function, which is what allows us to treat `OWMWeather` as a class, and rewrite
our functions so they're properties belonging to `this` (an instantiated
version of the object). If you're interested in diving a bit deeper into how
this works, take a look at Pivotal's great blog post about [JavaScript
constructors, prototypes, and the 'new' keyword] [pivotal blog].
```js
var OWMWeather = function() {
this.owmWeatherAPIKey = '';
this.owmWeatherXHR = function(url, type, callback) {
//...
};
//...
};
```
Well also need to change our JS application code to construct a new
`OWMWeather` object, and change our reference of `appMessageHandler()` in the
in the `Pebble.addEventListener('appmessage', ...)` callback to
`owmWeather.appMessageHandler()`.
```js
var owmWeather = new OWMWeather();
Pebble.addEventListener('ready', function(e) {
console.log('PebbleKit JS ready!');
});
Pebble.addEventListener('appmessage', function(e) {
console.log('appmessage: ' + JSON.stringify(e.payload));
owmWeather.appMessageHandler(e);
});
```
## Using the .bind() function
If we try to run our code at this point in time, we're going to run into a slew
of errors because we've changed the scope of the functions, but we haven't
updated the places where we call them. We need to change
`owmWeatherFunctionName()` to `this.owmWeatherFunctionName()` (same goes for
the API key). Here's what the new `owmWeatherLocationSuccess` method looks like
(changes are indicated with in-line comments):
```js
this.owmWeatherLocationSuccess = function(pos) {
// Change owmWeatherAPIKey to this.owmWeatherAPIKey
var url = 'http://api.openweathermap.org/data/2.5/weather?' +
'lat=' + pos.coords.latitude + '&lon=' + pos.coords.longitude +
'&appid=' + this.owmWeatherAPIKey;
console.log('owm-weather: Location success. Contacting OpenWeatherMap.org..');
this.owmWeatherXHR(url, 'GET', function(responseText) {
console.log('owm-weather: Got API response!');
if(responseText.length > 100) {
// Change owmWeatherSendToPebble(..) to this.owmWeatherSendToPebble(..)
this.owmWeatherSendToPebble(JSON.parse(responseText));
} else {
console.log('owm-weather: API response was bad. Wrong API key?');
Pebble.sendAppMessage({ 'OWMWeatherAppMessageKeyBadKey': 1 });
}
// Add .bind(this) to the closing brace of the callback
}.bind(this));
};
```
An observant developer might notice that along with changing `owmWeatherXHR` to
`this.owmWeatherXHR`, we've also added `.bind(this)` to the end of the callback
function.
We're not going to dive too deeply into how `this` and `bind` works (that's a
whole blog post on its own), but what I will say is that the `bind` method can
be thought of as modifying a function so that it will, when invoked, have its
`this` keyword set to the provided parameter.
> If you want to learn more about JavaScript Objects, scope, and `bind`, I will
> encourage you to read [You Don't Know JS: *this* & Object Prototypes] [js book].
We'll want use `bind(this)` (where `this` will be the instance of the
OWMWeather class) whenever we're using an OWMWeather method as a callback from
within the OWMWeather code.
```js
navigator.geolocation.getCurrentPosition(
this.owmWeatherLocationSuccess.bind(this),
this.owmWeatherLocationError.bind(this), {
timeout: 15000,
maximumAge: 60000
});
```
At this point, our code should look like [this] [owm lib 1].
## Using module.exports
The last thing we want to do to make this into a handy module is extract the
code into its own file (`/src/js/lib/owm_weather.js`), and use the
[module.exports] [module exports] API to export our OWMWeather class.
```js
var OWMWeather = new function() {
//...
};
module.exports = OWMWeather;
```
In order to use this in our PebbleKit JS application, we need to do a couple things..
If you're using CloudPebble:
- Change **JS Handling** to _CommonJS-style_ in the project settings
If you're using the SDK:
- Update our `appinfo.json` to include `'enableMultiJS': true` if it isn't
already there
- Rename `src/js/pebble-js-app.js` to `src/js/app.js`
Once we've made these changes to our files, we're ready to include OWMWeather
in our `app.js` file with the [require API] [require api].
```js
var OWMWeather = require('./lib/owm_weather.js');
var owmWeather = new OWMWeather();
Pebble.addEventListener('ready', function(e) {
console.log('PebbleKit JS ready!');
});
Pebble.addEventListener('appmessage', function(e) {
console.log('appmessage: ' + JSON.stringify(e.payload));
owmWeather.appMessageHandler(e);
});
```
At this point, our code should something look like [this][owm lib 2].
## Refactoring Variable and Function Names
Since weve moved all of the OWMWeather code into a class, we can safely remove
the `owmWeather` prefixes on all of our methods and properties. While were at
it, we're also going to rename functions and properties that are intended to be
private to begin with an `_`, which is fairly common practice:
```js
var OWMWeather = function() {
this._apiKey = '';
this._xhrWrapper = function(url, type, callback) {
//...
};
//...
};
```
## What's next..
And that's it - we've successfuly refactored our code into a module that should
be easier for developers to use and share (which is exactly what we want). You
can view the full source code for this blog post [here][owm lib final].
In the next blog post, we'll take this library a step further and look at how
you can abstract away the need for `appKeys` in your `appinfo.json` file to
make working with the library *even easier*...
Until then, happy hacking!
[sdk 3.9]: /sdk/changelogs/3.9/
[js blog]: /blog/2016/01/29/Multiple-JavaScript-Files/
[owm]: http://openweathermap.org/
[getcurrentposition]: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition
[pivotal blog]: https://blog.pivotal.io/labs/labs/javascript-constructors-prototypes-and-the-new-keyword
[js book]: https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/README.md#you-dont-know-js-this--object-prototypes
[module exports]: http://www.sitepoint.com/understanding-module-exports-exports-node-js/
[require api]: http://www.sitepoint.com/understanding-module-exports-exports-node-js/#importing-a-module
[owm lib 0]: https://github.com/pebble-hacks/owm-weather/tree/8c4f770e591fe5eff65209ebb6fe6ef23152d81a
[owm lib 1]: https://github.com/pebble-hacks/owm-weather/blob/8c9ab77a66d5c38719acbdfce939fbfac6d12235/owm_weather/owm_weather.js
[owm lib 2]: https://github.com/pebble-hacks/owm-weather/tree/597c717627c281b56ff303ca94f35789002a969e
[owm lib final]: https://github.com/pebble-hacks/owm-weather/tree/657da669c3d9309a956f655c65263b8dc06cec1f

View file

@ -0,0 +1,271 @@
---
# 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: Introducing App Debugging
author: katharine
tags:
- Freshly Baked
---
Happy leap day! Today is a once-every-four-years day of bizarre date-related
bugs, and thus an opportune moment for us to introduce our latest developer
feature: app debugging in the emulator! This gives you a powerful new way to
hunt down errors in your apps.
A new command is available in our preview release of pebble tool 4.2 and
SDK 3.10: `pebble gdb`.
Running this command from a project directory on an emulator with your app
installed will pause the emulator and attach a gdb debugger instance
to it:
```nc|text
katharine@scootaloo ~/p/pebble-caltrain (master)> pebble gdb --emulator basalt
Reading symbols from /Users/katharine/Library/Application Support/Pebble SDK/SDKs/3.10-beta4/sdk-core/pebble/basalt/qemu/basalt_sdk_debug.elf...(no debugging symbols found)...done.
Remote debugging using :49229
0x0801cd8c in ?? ()
add symbol table from file "/Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf" at
.text_addr = 0x200200a8
.data_addr = 0x20023968
.bss_addr = 0x200239b8
Reading symbols from /Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf...done.
Breakpoint 1 at 0x804aebc
Press ctrl-D or type 'quit' to exit.
Try `pebble gdb --help` for a short cheat sheet.
Note that the emulator does not yet crash on memory access violations.
(gdb)
```
Do note that once we pause execution by launching gdb, emulator buttons and
any pebble tool command that interacts with the emulator won't work until we
continue execution (using `continue` or `c`) or quit gdb.
Once we're here, we can add some breakpoints to the app in interesting places.
Here we are debugging my
[Caltrain app](https://github.com/Katharine/pebble-caltrain). Let's say I think
there's a bug in the search for the next train: I probably want to poke around
in [`find_next_train`](https://github.com/Katharine/pebble-caltrain/blob/f4983c748429127a8af85911cb123bd8c3bacb73/src/planning.c#L4).
We can run `b find_next_train` to add a breakpoint there:
```nc|text
(gdb) b find_next_train
Breakpoint 2 at 0x20020f1e: file ../src/planning.c, line 5.
(gdb)
```
Now we can use the `c` or `continue` command to set my app running again, until
it stops at `find_next_train`:
```nc|text
(gdb) c
Continuing.
```
The app runs as usual until we open a station, which causes it to look up a
train, where it hits the breakpoint and pauses the app so we can inspect it:
```nc|text
Breakpoint 2, find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:5
5 TrainTime *best[2] = {NULL, NULL};
(gdb)
```
Now we can see how we got here using the `backtrace` or `bt` command:
```nc|text
(gdb) bt
#0 find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738)
at ../src/planning.c:7
#1 0x200211b2 in next_train_at_station (station=13 '\r',
northbound=0x20025a0c <s_northbound>, southbound=0x20025a14 <s_southbound>)
at ../src/planning.c:76
#2 0x200215c8 in prv_update_times () at ../src/stop_info.c:106
#3 0x200216f8 in show_stop_info (stop_id=13 '\r') at ../src/stop_info.c:174
#4 0x200219f0 in prv_handle_menu_click (menu_layer=0x2002fe3c,
cell_index=0x2002ff0c, context=0x2002fe3c) at ../src/stop_list.c:57
#5 0x0805cb1c in ?? ()
#6 0x0805a962 in ?? ()
#7 0x0801ebca in ?? ()
#8 0x0801e1fa in ?? ()
#9 0x200202d6 in main () at ../src/main.c:23
#10 0x080079de in ?? ()
#11 0x00000000 in ?? ()
```
The `??` entries are inside the pebble firmware; the rest are in the Caltrain
app.
We can step forward a few times to get to an interesting point using the `step`
or `s` command:
```nc|text
(gdb) s
7 const time_t timestamp = time(NULL);
(gdb) s
8 const uint16_t minute = current_minute();
(gdb) s
current_minute () at ../src/model.c:183
183 const time_t timestamp = time(NULL);
(gdb)
```
Now we've stepped into another function, `current_minute`. Let's say we're
confident in the implementation of this (maybe we wrote unit tests), so we can
jump back up to `find_next_train` using the `finish` command:
```nc|text
(gdb) finish
Run till exit from #0 current_minute () at ../src/model.c:183
0x20020f38 in find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:8
8 const uint16_t minute = current_minute();
Value returned is $2 = 738
(gdb)
```
When we step to the next line, we see it has a similar `current_day` that we
don't need to inspect closely, so we jump over it using the `next` or `n`
command:
```nc|text
(gdb) s
9 const uint8_t day = current_day();
(gdb) n
11 for(int i = 0; i < count; ++i) {
(gdb)
```
Now we can double check our current state by using `info locals` to look at all
our local variables, and `info args` to look at what was originally passed in:
```nc|text
(gdb) info locals
i = 184
best = {0x0 <__pbl_app_info>, 0x0 <__pbl_app_info>}
timestamp = 1456776942
minute = 738
day = 1 '\001'
(gdb) info args
count = 184
times = 0x2002f414
nb = 0x2001873c
sb = 0x20018738
(gdb)
```
`timestamp`, `minute` and `day` all have the values they gained from our last
few function calls. `best` is still a pair of NULL pointers, and `i` hasn't been
assigned yet, so its value is garbage. Once we step another line it'll be filled
in, which we can check using the `print` or `p` command:
```nc|text
(gdb) s
12 TrainTime *train_time = &amp;times[i];
(gdb) p i
$3 = 0
```
Now let's step forward and have it fill in `train_time`, and see what we get:
```nc|text
(gdb) s
14 trip_get(train_time->trip, &amp;trip);
(gdb) p train_time
$4 = (TrainTime *) 0x2002f414
(gdb)
```
This is unenlightening — it's just the same pointer as `times`, which is what we
expect when referencing `&times[0]`. Fortunately, `print`/`p` will evaluate
arbitrary expressions, so we can dereference the pointer to see what it actually
points at:
```nc|text
(gdb) p *train_time
$5 = {trip = 189, time = 309, stop = 13 '\r', sequence = 10 '\n'}
(gdb)
```
Better! It might be more interesting to just print that out for each loop
iteration, so let's set a breakpoint here and have it print `*train_time`
and continue:
```nc|text
(gdb) b
Breakpoint 3 at 0x20020f62: file ../src/planning.c, line 14.
(gdb) commands
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>p *train_time
>c
>end
(gdb) c
Continuing.
Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:14
14 trip_get(train_time->trip, &amp;trip);
$6 = {trip = 209, time = 344, stop = 13 '\r', sequence = 11 '\v'}
Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:14
14 trip_get(train_time->trip, &amp;trip);
$7 = {trip = 199, time = 345, stop = 13 '\r', sequence = 13 '\r'}
```
…and so on. A bit noisy, so let's remove that breakpoint now:
```nc|text
(gdb) delete 3
(gdb)
```
Finally, let's have our program continue on its way by running `c` again:
```nc|text
(gdb) c
Continuing
```
When we want to get out of gdb we'll need our `(gdb)` prompt back, so press
ctrl-C to pause the app again:
```nc|text
^C
Program received signal SIGINT, Interrupt.
0x08007072 in ?? ()
(gdb)
```
This will most likely pause execution inside some firmware code, as we did when
we initially launched gdb. We can now do anything we've done before, but we're
just going to quit:
```nc|text
(gdb) quit
A debugging session is active.
Inferior 1 [Remote target] will be killed.
Quit anyway? (y or n) y
katharine@scootaloo ~/p/pebble-caltrain (master)>
```
Hopefully this has given you some ideas as to how you might be able to use gdb
to debug your own apps. If you'd like to know more about gdb,
[here is a Q&A-style tutorial](http://www.unknownroad.com/rtfm/gdbtut/) that
will answer many questions you might have. Good luck and happy debugging!

View file

@ -0,0 +1,206 @@
---
# 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: Take the Pebble Health API in your Stride
author: jonb
tags:
- Freshly Baked
---
I've been desperate to write a blog post about the Pebble [HealthService API]
(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/)
since it's initial release last month, and now I can finally share its
awesomeness with you. I'm also going to show you how you can use this exciting
new API to build an ultra-cool clone of the [Stride watchface]
(https://apps.getpebble.com/en_US/application/56b15c5c9c4b20ed5300006c) by
Pebble.
## Background
Back in December 2015, Pebble Health was launched and had a massive uptake from
the existing Pebble userbase. For some reason, I foolishly thought health was
purely aimed at fitness fanatics and I completely underestimated its potential
uses and benefits to non-athletes like me. To give you a bit of my background,
I'm an overweight father of two, I don't get much sleep and probably spend an
unhealthy amount time in front of a computer screen. I enabled Pebble Health,
and as time progressed, I began to see how little exercise and how little sleep
I actually get.
Once you have started visualising health information, you can begin to see
patterns and get an understanding of what 'good' days and 'bad' days look like.
You can then make incremental changes and see how that affects your mood and
quality of life, though to be completely honest, I haven't changed my bad habits
just yet. I haven't suddenly started running every day, nor have I magically
started getting &gt;5 hours sleep each night, but I now have a wealth of data
available to help shape my future lifestyle decisions.
## What health data is available?
The HealthService API exposes various [`HealthMetric`]
(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/#HealthMetric)
values which relate to the user being physically active, or sleeping.
- `HealthMetricStepCount` - The number of steps counted.
- `HealthMetricActiveSeconds` - The number of seconds spent
active (i.e. not resting).
- `HealthMetricWalkedDistanceMeters` - The distance
walked, in meters.
- `HealthMetricSleepSeconds` - The number of seconds spent
sleeping.
- `HealthMetricSleepRestfulSeconds` - The number of sleep
seconds in the 'restful' or deep sleep state.
- `HealthMetricActiveKCalories` - The number of kcal
(Calories) burned while active.
- `HealthMetricRestingKCalories` - The number of kcal
(Calories) burned while resting due to resting metabolism.
## Querying the health data
There are a few important things you need to do before you actually attempt to
access the data from the HealthService.
1. Indicate that your watchapp will be accessing Health data by adding `health`
to the `capabilities` section of the `appinfo.json`, or checking the Uses Health
option in your project settings in CloudPebble. This is used by the Pebble Time
mobile app to inform users who are installing your watchapp that you're going to
be accessing their health data. If you dont do this and attempt to access a
health api, your app will be terminated.
2. Check if the user has enabled Pebble Health in the Pebble Health app settings
and that there is any health data available. This is done by checking the
[`HealthServiceAccessibilityMask`]
(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/#HealthServiceAccessibilityMask)
using [`health_service_metric_accessible()`]
(https://developer.pebble.com/docs/c/Foundation/Event_Service/HealthService/#health_service_metric_accessible).
Here is a basic example of the steps you should take before attempting to access
health data:
```c
// Between midnight (the start time) and now (the end time)
time_t start = time_start_of_today();
time_t end = time(NULL);
// Check step count data is actually available
bool any_data_available = HealthServiceAccessibilityMaskAvailable &
health_service_metric_accessible(HealthMetricStepCount, start, end);
```
## Building a Stride clone
![stride](/images/blog/2016-03-07-image03.png)
<p style="text-align: center; font-size: 0.9em;">Stride by Pebble</p>
The Stride watchface displays the current time and your current step count for
today. Around the edge of the screen it displays a bar which is your progress
towards your average daily step count. The yellow line shows your daily average
step count for the current time of day. So as the day progresses, the yellow
line moves towards the end of your total step goal. If you manage to get ahead
of the yellow line, youre on track to beat your daily step count and to
highlight this, all of the blue elements turn green.
We're going to break down this project into several steps, so you can easily
see what's going on. I'm going to cheat slightly to keep the examples short.
Specifically we'll use emojis instead of the shoe icon, we won't be displaying
the am/pm indicator and well only be drawing circular progress bars on all
platforms.
## Step 1 - Building the basic watchface
![strider-step1](/images/blog/2016-03-07-image00.png)
We'll start by creating a fairly typical structure of a watchface. We
initialise a new `Window`, add a `TextLayer` to display the time, then set up a
tick handler to update the time every minute.
[View the source code]
(https://github.com/pebble-examples/strider-watchface/tree/step1/src/Strider.c)
## Step 2 - Add the step count & icon
![strider-step2](/images/blog/2016-03-07-image07.png)
Now we need to add a new `TextLayer` to display our step count and emoji. We're
going to need to query the HealthService API to retrieve:
- The current step count for today.
- The target step goal amount.
- The average step count for the current time of day.
The step goal only needs to be updated once per day and we're going to use the
[`HealthEventMovementUpdate`]
(https://developer.pebble.com/guides/pebble-apps/sensors/health/#subscribing-to-healthservice-events)
event to trigger an update for the other data. This event will probably trigger
multiple times per minute, so if youre looking to conserve energy, you could
manually update the data. Stride also changes the color of the text and icon if
the user has exceeded their average step count, so we'll do that too.
[View the source code]
(https://github.com/pebble-examples/strider-watchface/tree/step2/src/Strider.c)
## Step 3 - Add the progress indicators and progress bar
![strider-step3](/images/blog/2016-03-07-image04.png)
Were going to create 2 new layers, one for the grey progress indicator dots
and the other for the progress bar. To simplify the example, were just dealing
with a circular indicator. If you want to see how Stride actually draws
rectangularly, checkout the [health-watchface on Github]
(https://github.com/pebble-examples/health-watchface).
The dots layer update procedure calculates the coordinates for each point using
[`gpoint_from_polar()`]
(https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/#gpoint_from_polar)
and then draws a circle at that point, for each dot. The progress layer update
procedure uses [`graphics_fill_radial()`]
(https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/#graphics_fill_radial)
which can fill a circle from a start and end angle, were using a narrow inset
thickness so that the circle is just drawn as a ring.
We also need to redraw the progress layer when the step count changes, but all
we need to do is mark the layer dirty.
[View the source code]
(https://github.com/pebble-examples/strider-watchface/tree/step3/src/Strider.c)
## Step 4 - Add the average step indicator
![strider-step4](/images/blog/2016-03-07-image01.png)
The final thing were going to add is an indicator to show your average daily
steps for this time of day. Weve already calculated the average, and were
going to use [`graphics_fill_radial()`]
(https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/#graphics_fill_radial)
again but this time its just to draw a yellow line. Well need to add another
new layer and update procedure to handle the drawing of the line.
We also need to redraw the new layer when the step average value changes, but
again, all we need to do is mark the layer dirty.
[View the complete source code]
(https://github.com/pebble-examples/strider-watchface)
## The finished product
![strider-step2b](/images/blog/2016-03-07-image05.jpg)
<p style="text-align: center; font-size: 0.9em;">Strider watchface</p>
## Final thoughts
Ive really only scratched the surface of the HealthService API, but hopefully
youre now sufficiently excited to build something awesome! Wed really love to
see how you use it, and If you create a health enabled watchapp or watchface,
dont forget to let us know on [Twitter](http://twitter.com/pebbledev) or on
[Discord]({{ site.links.discord_invite }})!

View file

@ -0,0 +1,149 @@
---
# 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.js Support for Chalk!
author: meiguro
tags:
- Freshly Baked
---
After many morning coding sessions on the weekends,
[Pebble.js](/docs/pebblejs/) has been updated to
support the Chalk platform (Pebble Time Round). This update is available in
[CloudPebble]({{site.links.cloudpebble}}), and ready for you to work with right in
your browser with no setup!
Supporting Chalk required a ton of bug crushing to become compatible with the
new 3.0 style status bar, changes to accommodate reduced memory on the 3.x
Aplite platform, adding new features such as text flow and paging, and making it
easier to target multiple platforms by adding a feature detection API.
![Pebble.js Screenshots](/images/blog/2016-05-13-pebble-js-round/pebblejs.png)
<p class="blog__image-text">Pebble.js running on all available platforms.</p>
Whether or not youre targeting the Chalk platform, there are many new features
for you to explore in this update - and simply recompiling your project with the
latest version of Pebble.js will update the look and feel through refined
spacing between text fields.
The background color of window's status bar can now be changed, allowing it to
blend in with the app's background in a similar way to Pebble's Music app.
```js
var defaultBackgroundColor = 'white';
var defaultColor = 'black';
var card = new UI.Card({
backgroundColor: defaultBackgroundColor,
status: {
color: defaultColor,
backgroundColor: defaultBackgroundColor
}
});
card.show();
```
Two new elements were added,
[Line](/docs/pebblejs/#line) and
[Radial](/docs/pebblejs/#radial), and all elements
now include [borderWidth](/docs/pebblejs/#element-
borderwidth- width), [borderColor](/docs/pebblejs
/#element- bordercolor-color) and
[backgroundColor](/docs/pebblejs/#element-
backgroundcolor-color) properties (except `Line`, which uses
[strokeWidth](/docs/pebblejs/#line-strokewidth-
width) and [strokeColor](/docs/pebblejs/#line-
strokecolor-color) instead).
There are many bugfixes as well. Scrolling is no longer jittery for cards and
menus, the status bar will no longer disappear upon changing windows that both
requested to have a status bar, and large bodies of text are truncated in cards
instead of just not showing up.
There is one new known issue - which causes some applications with heavy memory
usage to crash. If you're experiencing this issue, we would appreciate you
adding more details to [#161](https://github.com/pebble/pebblejs/issues/161).
This update also comes with two new guides to help familiarize yourself with the
exciting new world of round and colorful apps. [Using
Color](/docs/pebblejs/#using-color) will help you
understand all the different ways you can specify which color you want your text
(and other elements) to be, and how you would go about styling your app with
color. [Using Feature](/docs/pebblejs/#using-
feature) will help you understand the new Feature API, how it can be used to
specify different behaviours and UIs depending on what platform you're running
on, and what features it includes.
## CloudPebble
Enabling Chalk support in CloudPebble is simple. Open your project's "Settings"
screen, check the "Build Chalk" box, then recompile your project.
![CloudPebble Settings Screen](/images/blog/2016-05-13-pebble-js-round/settings.png)
<p class="blog__image-text">Cloud Pebble Settings Screen.</p>
To run your project on CloudPebble's Chalk emulator, compile your project, then
open the "Compilation" sceen, and select "Emulator" then "Chalk."
## Pebble Tool + Local SDK
If you're using the Pebble Tool and local SDK, you'll need to merge the `master`
branch of the Pebble.js repositoroy into your project, then edit the
`appinfo.json` file to include `chalk` in the `targetPlatforms` array.
```
{
"uuid": "8e02b5f5-c0fb-450c-a568-47fcaadf97eb",
"shortName": "Test",
"longName": "Test Application",
"companyName": "#makeawesomehappen",
"versionLabel": "0.1",
"sdkVersion": "3",
"enableMultiJS": true,
"targetPlatforms": ["aplite", "basalt", "chalk"],
"watchapp": {
"watchface": false
},
"appKeys": { },
"resources": { }
}
```
To run your project with command line emulator, compile your project, then
install it to the Chalk emulator:
```
> pebble clean
> pebble build && pebble install --emulator=chalk --logs
```
Remember, were also working on supporting native JavaScript applications on
Pebble smartwatches, and are calling it
[Rocky.js](https://pebble.github.io/rockyjs/). Rest assured, I will continue to
maintain Pebble.js, and you can continue to develop and ship apps with it! Keep
an eye out on future developments by following the [GitHub
repository](https://github.com/pebble/pebblejs) and checking out the latest
commits.
Let us know abour Pebble.js project or anything else you feel like mentioning,
by shouting on Twitter to [@PebbleDev](https://twitter.com/pebbledev),
[@Pebble](https://twitter.com/pebble) or even myself
[@Meiguro](https://twitter.com/meiguro).
Heres to more exciting weekend coding sessions in the future for both of us!

View file

@ -0,0 +1,74 @@
---
# 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: Introducing Pebble Packages
author: katharine
tags:
- Freshly Baked
---
I am thrilled to announce that, as part of SDK 3.13 and Pebble Tool 4.3, we have
launched our own packaging system: Pebble Packages!
There are already a healthy selection of Pebble libraries to be had, such as
Yuriy's excellent [EffectLayer](https://github.com/ygalanter/EffectLayer),
Reboot's Ramblings'
[palette manipulator](https://github.com/rebootsramblings/GBitmap-Colour-Palette-Manipulator),
or even our own [weather library](https://github.com/pebble-hacks/owm-weather).
However, using them today is inelegant: you generally have to copy/paste all of
the library code into your own project and follow some other setup instructions:
assigning appmessage keys, editing resources into your appinfo.json, or similar.
Starting today, using a Pebble Package can be as simple as running
`pebble package install pebble-owm-weather`. A Pebble Package can contain
both C code for the watch and JavaScript code for the phone. Furthermore, it can
also contain any resources, and define its own appmessage keys. All numeric
identifiers will be resolved at app build time to ensure there are never any
conflicts. This helps to enable zero-setup packages of all kinds.
To make the usage of appmessages in Pebble Packages possible, we have also
substantially improved the functionality of the old `appKeys` section of
appinfo.json. We will now automatically insert the keys into your C code with
the prefix `MESSAGE_KEY_`. You can also access their numeric values from
JavaScript by requiring the `message_keys` module:
`var keys = require('message_keys')`.
Packages would be nothing without a package manager, so we have built our
packaging system on top of the excellent [npm](https://npmjs.com) package manager. This gives
us support for dependency management, versioning, package hosting, and more. Furthermore,
some traditional JavaScript modules on npm will work out of the box, as long as
they don't depend on running in node or on a browser. As part of this
move we have **deprecated appinfo.json**: we now use `package.json` instead.
The latest version of the Pebble Tool can convert your project when you run
`pebble convert-project`. Old-style projects continue to be supported, but
cannot use the package manager.
For your convenience, we have provided some wrappers around npm functionality:
* `pebble package install` to safely install a package.
* `pebble package uninstall` to uninstall a package.
* `pebble package login` to log in to or create your npm account.
* `pebble package publish` to publish your package to npm.
We also have UI in CloudPebble to enter your dependencies.
You can browse the available packages on npm under the
[pebble-package](https://www.npmjs.com/browse/keyword/pebble-package) keyword,
and read our guides on [using](/guides/pebble-packages/using-packages/) and
[creating](/guides/pebble-packages/creating-packages/) Pebble Packages.
I'm looking forward to seeing what you make!

View file

@ -0,0 +1,186 @@
---
# 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: A Wild SDK Appears
author: cat
tags:
- Freshly Baked
---
Developers rejoice - weve released the first version of our [SDK 4](/sdk4)
developer preview! This SDK enables you to start building applications for the
new Diorite platform (Pebble 2), and includes a set of new APIs for interacting
with the 4.0 user experience.
In this blog post well dive into the [App Glance](/docs/c/Foundation/App_Glance/),
[`UnobstructedArea`](/docs/c/User_Interface/UnobstructedArea/), and
[`AppExitReason`](/docs/c/Foundation/Exit_Reason/) APIs, and explain
how you can use them to create great experiences for your users!
## App Glance API
Lets start with the App Glance API, which allows applications to present
information for the launcher to display. The information an application displays
in the launcher is called an app glance - and the information that is displayed
at any particular point in time is referred to as an [`AppGlanceSlice`](/docs/c/Foundation/App_Glance/#AppGlanceSlice).
![Virtual Pet App >{pebble-screenshot,pebble-screenshot--time-black}](/images/blog/2016-06-15-sdk4-preview/virtual-pet.png)
`AppGlanceSlice`s have expiration times, which means you can add multiple slices
at once. Slices will be displayed in the order they are added and removed at
their specified expiration times.
```c
static void prv_update_app_glance(AppGlanceReloadSession *session, size_t limit, void *context) {
// This shouldn't happen, but developers should always ensure they have
// sufficient slices, or adding slices may fail.
if (limit < 1) return;
// !! When layout.icon_resource_id is not set, the app's default icon is used
const AppGlanceSlice entry = (AppGlanceSlice) {
.layout = {
.template_string = "Hello from the app glance"
},
.expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION
};
// Add the slice, and store the result so we can check it later
AppGlanceResult result = app_gpance_add_slice(session, entry);
}
```
To dig into this feature, we recommend you start with our new
[AppGlance C API guide](/guides/user-interfaces/appglance-c), and also give the
[API docs](/docs/c/Foundation/App_Glance/) a quick read.
> We are planning to extend the AppGlance API to include a PebbleKit JS API, as
> well as a HTTP web API, intended to allow developers to push `AppGlanceSlice`s
> from external web services to their users' smartwatches.
## UnobstructedArea API
The [`UnobstructedArea`](/docs/c/User_Interface/UnobstructedArea/) API
allows developers to create watchfaces capable of sensing, and responding to
Timeline Quick View events. Timeline Quick View is a new feature in SDK 4.0,
which displays upcoming and ongoing events on top of the users watchface. The
functionality added through the `UnobstructedArea` API allows developers to get
the unobstructed bounds of a layer, or subscribe to events related to the
watchfaces unobstructed area changing.
```c
static int s_offset_top_percent = 33;
static int s_offset_bottom_percent = 20;
static void prv_unobstructed_change(AnimationProgress progress, void *context) {
// Get the total available screen real-estate
GRect bounds = layer_get_unobstructed_bounds(window_layer);
// Shift the Y coordinate of the top text layer
GRect top_frame = layer_get_frame(text_layer_get_layer(s_top_text_layer));
top_frame.origin.y = bounds.size.h * s_offset_top_percent / 100;
layer_set_frame(top_frame, text_layer_get_layer(s_top_text_layer));
// Shift the Y coordinate of our bottom text layer
GRect bottom_frame = layer_get_frame(text_layer_get_layer(s_bottom_text_layer));
bottom_frame.origin.y = bounds.size.h * s_offset_bottom_percent / 100;
layer_set_frame(bottom_frame, text_layer_get_layer(s_bottom_text_layer));
}
static void prv_main_window_load(Window *window) {
unobstructed_area_service_subscribe((UnobstructedAreaHandlers) {
.change = prv_unobstructed_change
}, NULL);
}
```
> We encourage developers to begin exploring what their existing watchfaces will
> look like when the system is displaying the Timeline Quick View dialog, and
> adjust their designs to provide the best experience possible for users.
Take a look at our [UnobstructedArea API Guide](/guides/user-interfaces/unobstructed-area/)
and the [API documentation](/docs/c/User_Interface/UnobstructedArea/) to
get started!
## AppExitReason API
One of the APIs we havent talked about as much is the
[`AppExitReason`](/docs/c/Foundation/Exit_Reason/) API, which enables
developers to specify why their app exited, and determines whether the system
returns the user to the launcher, or the active watchface. Take a look at our
[AppExitReason API Guide](/guides/user-interfaces/app-exit-reason),
and read [the API docs](/docs/c/Foundation/Exit_Reason) to learn more.
This feature enables developers to create "One Click Action" watchapps, that
perform an action, then immediately return the user to the watchface to create a
simple, fluid experience.
![Uber App >{pebble-screenshot,pebble-screenshot--time-black}](/images/blog/2016-06-15-sdk4-preview/uber.gif)
To help get you started thinking about and designing One Click Action apps,
weve created a [One Click Action App Guide](/guides/design-and-interaction/one-click-actions)
around the relatively minimal example of locking and unlocking your front door
(with a [Lockitron lock](https://lockitron.com)).
## 4.0 Emulator
The SDK 4 preview also includes an update to the emulator, which not only adds
support for Pebble 2, but includes the updated 4.0 user interface, and a few
other goodies that were sure developers will love.
The new emulator includes a launcher capable of displaying watchapps glances,
and can be accessed by pressing the `Select` button from the watchface.
CloudPebble and the Pebble Tool also include new functionality to enable
developers to toggle Timeline Quick View, allowing you to make sure your
watchface looks good in every context!
Finally, weve added the ability to install multiple watchfaces and watchapps
into the emulator. Watchfaces and watchapps installed into the emulator will
remain installed (even if the emulator is closed) until `pebble wipe` is called
from the command line.
Due to limitations with CloudPebble, this feature is currently only available in
the Pebble Tool.
## Whats Next
The SDK 4 developer preview is exactly that, a preview. Youve probably noticed
a few really important and exciting features we didnt mention - the Heart Rate
API has been designed, but is not yet fully implemented, and we have not yet
added the ability to build applications for the Emery Platform (Pebble Time 2).
The official SDK 4 release is currently planned for the end of August - and it
will include not only the Heart Rate API, and support for building Emery
applications, but the first version of Pebbles fantastic new embedded
JavaScript SDK, [Rocky.js](http://pebble.github.io/rockyjs/).
## Show Us What You Make
While weve been conceptualizing and designing the new functionality in SDK 4
for quite a long time, the API itself is relatively new, even for those of us
lucky enough to be on the inside - so weve only really just begun to see
whats possible, and were more than excited to see what youll build with this
new functionality.
Send us a link to something youve built on Twitter (
[@pebbledev](https://twitter.com/pebbledev)) and well look at including it in
an upcoming newsletter (and send some swag your way).
Happy Hacking!
Team Pebble

View file

@ -0,0 +1,117 @@
---
# 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: Introducing Clay - App configuration made easy
author: keegan
tags:
- Freshly Baked
---
It is with great pleasure that I present to you, Clay, a Pebble package that makes it easy to add offline configuration pages to your Pebble apps. All you need to get started is a couple lines of JavaScript and a JSON file; no servers, HTML or CSS required.
![Clay Example](/images/blog/2016-06-24-introducing-clay/clay-example.png =421)
## How to get started
This post aims to provide high level explanation of how the framework works and how it came to be. Below is a quick overview of how easy it is to make your app configurable with Clay. If you would like to learn how to integrate Clay into your project, read our guide on [app configuration](/guides/user-interfaces/app-configuration/) or read the full documentation on the project [GitHub repository.](https://github.com/pebble/clay#clay)
**1) Install the module via [Pebble Packages](/guides/pebble-packages/using-packages/)**
- SDK: `$ pebble package install pebble-clay`
- CloudPebble: Add `pebble-clay`, version `^1.0.0` to your project dependencies.
**2) Create a configuration file that looks something like:**
```js
module.exports = [
{
"type": "heading",
"defaultValue": "Example App"
},
{
"type": "section",
"items": [
{
"type": "color",
"messageKey": "BackgroundColor",
"defaultValue": "0x000000",
"label": "Background Color"
},
{
"type": "toggle",
"messageKey": "Animations",
"label": "Enable Animations",
"defaultValue": false
}
]
},
{
"type": "submit",
"defaultValue": "Save Settings"
}
];
```
**3) Update your project settings with the matching `messageKeys` from the config above.**
**4) Add a few lines to your `index.js`**
```js
var Clay = require('pebble-clay');
var clayConfig = require('./config');
var clay = new Clay(clayConfig);
```
**5) Retrieve the values on the C side using standard [app messages](/guides/communication/sending-and-receiving-data/) or alternatively let [enamel](https://github.com/gregoiresage/enamel) do the work for you.**
## Why I created Clay
Clay began as a side project of mine. Whilst developing my [Segment watchface](https://apps.pebble.com/applications/560ae4754d43a36393000001), I wanted to add a configuration page to allow users to customize the colors of the watchface. However, I soon discovered this process to be rather fiddly. The old way of doing things required you to create and host HTML pages even for simple things like changing a background color or toggling an option. You would also need to write a bunch of boilerplate JavaScript to serialize and send the settings to the watch. Best case, for developers, this is super tedious. Worst case, it is terrifying. By day, I work as a web developer (mainly front-end) so if I was finding the process tiresome, I could only imagine how challenging it would be for someone who wasn't familiar with web technologies. And so I decided to create a framework that would alleviate this barrier for developers. I had a number of requirements that needed to be met in order for the framework to achieve my goals:
- It should not require developers to write any HTML or CSS.
- It should use JSON to define the generated config page.
- It should all work offline
- Developers should be able to add interactivity to the config page without manually manipulating DOM.
- Developers should be able to create and share their own custom components.
- The config page should be able to be versioned with the rest of the code in the project.
- It should have extensive unit tests with 100% test coverage.
## How Clay Actually Works
Clay has two main components. The first is a very long string that is compiled from all the HTML, CSS and JavaScript that forms the skeleton of the generated config page. The second is the module that gets included in your `index.js`. This module is in charge of bundling all the dynamic elements set by the developer into a format that can be opened using `Pebble.openURL()`. It is also in charge of listening for the `webviewclosed` event and persisting the user's settings to local storage. I use Gulp and a series of Browserify transforms to compile all these components into a single module. The advantage of using a system such as Browserify, is that later, Clay can be made into a stand alone module to be used by developers who have very advanced use cases that require Clay to be run in a hosted HTML page.
The most challenging item on the requirements list was making the whole thing work offline. Neither of the Pebble mobile apps allow file system access from the config page's webview, so I had to get a little creative. It turns out that [Data URIs](https://en.wikipedia.org/wiki/Data_URI_scheme) work with more than images. You can in fact encode an entire HTML page into the URI. This was the solution to making the config pages offline. It sounds crazy but this method actually provides other advantages for developers beyond offline access. By bundling all the HTML, CSS and JavaScript into the Clay package, the version of Clay being used by the developer will not change without the developer rebuilding their app. This means developers do not need to worry about the behavior of their app's configuration page potentially breaking as new versions of Clay get released.
## Advanced Use
If developers want to add additional functionality to their config page, they can. Clay allows developers to inject, what I call a [`custom function`](https://github.com/pebble/clay#custom-function) into the generated config page. This allows developers control of their config page without needing to manipulate the DOM. Clay achieves this by exposing an API that provides a consistent way of interacting with the config page as well as making AJAX requests. Instead of manually updating the values of HTML elements, developers can use the much simpler Clay API.
## Where to find more information
- [App configuration guide.](/guides/user-interfaces/app-configuration/)
- [Clay GitHub repository including full documentation.](https://github.com/pebble/clay)
- Chat to us in the `#clay` channel on [Discord]({{ site.links.discord_invite }}).
- Visit the [Pebble Forums](https://forums.pebble.com/)
- Tweet at [@pebbledev](https://twitter.com/pebbledev)
## How to get involved
Clay is open source and welcomes pull requests from anybody. Have a look at the [contributing guide](https://github.com/pebble/clay/blob/master/CONTRIBUTING.md) for instructions on how you can help make Clay even better. Regardless of your skill level, if you have any ideas on how Clay could be improved or if you find any bugs, we would love you to [submit an issue on GitHub](https://github.com/pebble/clay/issues/new).

View file

@ -0,0 +1,291 @@
---
# 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: Introducing Rocky.js Watchfaces!
author: jonb
tags:
- Freshly Baked
---
We're incredibly excited and proud to announce the first public beta version of
Rocky.js watchfaces for Pebble. Rocky.js is ECMAScript 5.1 JavaScript running
natively on Pebble smartwatches, thanks to
[our collaboration](https://github.com/Samsung/jerryscript/wiki/JerryScriptWorkshopApril2016)
with [JerryScript](https://github.com/pebble/jerryscript). This is an incredible
feat of engineering!
## It's JavaScript on the freakin' watch!
```javascript
var rocky = require('rocky');
rocky.on('minutechange', function(event) {
rocky.requestDraw();
});
rocky.on('draw', function(event) {
var ctx = event.context;
ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
var w = ctx.canvas.unobstructedWidth;
var h = ctx.canvas.unobstructedHeight;
ctx.fillText('JavaScript\non the watch!', w / 2, h / 2);
});
```
![Rocky >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-08-16-jotw.png)
Right now we're giving you early access to this game-changing environment to
create watchfaces entirely in JavaScript. Feel free to share your code, to
install it on the emulator and on your watch (with the upcoming firmware 4.0),
but please don't upload apps to the appstore as they will stop working in a
future release.
<div class="alert alert--fg-white alert--bg-purple">
{% markdown %}
**Appstore Publishing**
Rocky.js JavaScript watchfaces must not be published into the appstore at
this time.
{% endmarkdown %}
</div>
We can't wait to hear your feedback and see what amazing watchfaces you create!
Enough hype, let's dive into the eye of the tiger!
## Rocky.js Projects
Things are a little different in the JavaScript world, so let's take a closer
look at the structure of a Rocky.js project:
```nc|text
package.json
src/rocky/index.js
src/pkjs/index.js
common/*.js
```
#### package.json
The format of the `package.json` file remains roughly the same as existing
Pebble projects, with some minor differences:
* `"projectType": "rocky"`.
* Aplite `targetPlatform` is not supported.
* `resources` are not currently supported, but will consume space in your PBW if
specified.
* `messageKeys` are not permitted.
#### src/rocky/index.js
This is now the main entry point into our application on the watch. This is
where our Rocky.js JavaScript code resides. All code within this file will be
executed on the smartwatch. Additional scripts may be placed in this folder, see
[below](#additional-scripts).
#### src/pkjs/index.js
This file contains our [PebbleKit JS](/docs/pebblekit-js/) (pkjs) JavaScript
code. This code will execute on the mobile device connected to the smartwatch.
Additional scripts may be placed in this folder, see
[below](#additional-scripts).
For Rocky.js projects only, we've added a new simplified communication channel
[`postMessage()`](/docs/rockyjs/rocky/#postMessage) and
[`on('message', ...)`](/docs/rockyjs/rocky/#on) which allows you to send and
receive JavaScript JSON objects between the phone and smartwatch.
#### Additional Scripts
Both the `rocky` and `pkjs` folders support the use of multiple .js files, which
helps to keep your code clean and modular. Use the
[CommonJS Module](http://www.commonjs.org/specs/modules/1.0/) format for
your additional scripts, then `require()` them in your `index.js`.
```javascript
// file: src/rocky/additional.js
function test() {
console.log('Additional File');
}
module.exports.test = test;
```
```javascript
// file: src/rocky/index.js
var additional = require('./additional');
additional.test();
```
#### common/*.js
If you need to share code between `rocky` and `pkjs`, you can create a `common`
folder, then add your JavaScript files.
```javascript
// file: src/common/shared.js
function test() {
console.log('Hello from shared code');
}
module.exports.test = test;
```
```javascript
// file: src/rocky/index.js
var shared = require('../common/shared');
shared.test();
```
```javascript
// file: src/pkjs/index.js
var shared = require('../common/shared');
shared.test();
```
## Available APIs
In this initial release of Rocky.js, we have focused on the ability to create
watchfaces only. We will be adding more and more APIs as time progresses, and
we're determined for JavaScript to have feature parity with the rest of the
Pebble developer ecosystem.
We've developed our API in-line with standard Web APIs, which may appear strange
to existing Pebble developers, but we're confident that this will facilitate
code re-use and provide a better experience overall.
### System Events
We've provided a series of events which every watchface will likely require, and
each of these events allow you to provide a callback method which is emitted
when the event occurs.
Existing Pebble developers will be familiar with the tick style events,
including:
[`secondchange`](/docs/rockyjs/rocky/#on),
[`minutechange`](/docs/rockyjs/rocky/#on),
[`hourchange`](/docs/rockyjs/rocky/#on) and
[`daychange`](/docs/rockyjs/rocky/#on). By using these events, instead of
[`setInterval`](/docs/rockyjs), we're automatically kept in sync with the wall
clock time.
We also have a
[`message`](/docs/rockyjs/rocky/#on) event for
receiving JavaScript JSON objects from the `pkjs` component, and a
[`draw`](/docs/rockyjs/rocky/#on) event which you'll use to control the screen
updates.
```javascript
rocky.on('minutechange', function(event) {
// Request the screen to be redrawn on next pass
rocky.requestDraw();
});
```
### Drawing Canvas
The canvas is a 2D rendering context and represents the display of the Pebble
smartwatch. We use the canvas context for drawing text and shapes. We're aiming
to support standard Web API methods and properties where possible, so the canvas
has been made available as a
[CanvasRenderingContext2D](/docs/rockyjs/CanvasRenderingContext2D/).
> Please note that the canvas isn't fully implemented yet, so certain methods
> and properties are not available yet. We're still working on this, so expect
> more in future updates!
```javascript
rocky.on('draw', function(event) {
var ctx = event.context;
ctx.fillStyle = 'red';
ctx.textAlign = 'center';
ctx.font = '14px Gothic';
var w = ctx.canvas.unobstructedWidth;
var h = ctx.canvas.unobstructedHeight;
ctx.fillText('Rocky.js Rocks!', w / 2, h / 2);
});
```
## Limitations
We are still in the early beta phase and there are some limitations and
restrictions that you need to be aware of:
* Don't publish Rocky.js watchfaces to the Pebble appstore yet, they will stop
working.
* Rocky.js watchfaces only run on the 4.0 emulators/firmware. The 4.0 firmware
will be available soon for Basalt, Chalk and Diorite.
* No support for custom fonts, images and other resources, yet.
* No C code allowed.
* No messageKeys.
* There are file size and memory considerations with your Rocky.js projects.
If you include a large JS library, it probably won't work.
## SDK 4.0
We have published an updated version of
[CloudPebble]({{site.links.cloudpebble}}) which now supports creating
Rocky.js watchfaces in JavaScript.
If you prefer our local tools, we've also published everything you need to begin
creating Rocky.js watchfaces. Run the following command to install the Pebble
Tool and 4.0 SDK:
```nc|text
$ brew upgrade pebble-sdk
$ pebble sdk install latest
```
## How to Get Started
We created a [2-part tutorial](/tutorials/js-watchface-tutorial/part1/) for
getting started with Rocky.js watchfaces. It explains everything you need to
know about creating digital and analog watchfaces, plus how to retrieve
weather conditions from the internet.
If you're looking for more detailed information, check out the
[API Documentation](/docs/rockyjs).
## Sample Watchfaces
We've already created a few sample watchfaces:
* [Tictoc](https://github.com/pebble-examples/rocky-watchface-tutorial-part1) -
Simple analog watchface.
* [Tictoc Weather](https://github.com/pebble-examples/rocky-watchface-tutorial-part2) -
Simple analog watchface with weather data from a REST API.
* [Leco with Weather](https://github.com/orviwan/rocky-leco-weather) - Simple
digital watchface with weather data from a REST API.
* [Leco with Clay](https://github.com/orviwan/rocky-leco-clay) - Simple analog
watchface which uses Clay for configurable settings.
* [Simplicity](https://github.com/orviwan/simplicity-rockyjs) - Rocky.js version
of the classic Simplicity watchface.
## Feedback and Help
We hope you're as exicited about Rocky.js as we are. If you need assistance,
have feedback or just want to send us some love, we're in #rockyjs on
[Discord]({{ site.links.discord_invite }}) or the
[Pebble developer forums](https://forums.pebble.com/c/development).
We're already working on the next update of Rocky.js and your feedback will help
shape the future!

View file

@ -0,0 +1,165 @@
---
# 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: Prime Time is Approaching for OS 4.0
author: jonb
tags:
- Freshly Baked
---
Pebble developers of the world unite! Pebble OS 4.0 is just around the corner
and it's now time to put those finishing touches on your projects, and prepare
them for publishing!
Pebble Time owners will be receiving OS 4.0 before Kickstarter backers receive
their Pebble 2 watches, so we're recommending that developers publish their
4.0 watchapps and watchfaces into the appstore **from August 31st onwards**.
We'll be promoting new and updated apps which utilize the new SDK 4.0 APIs and
features by creating a brand new category in the appstore called
'Optimized for 4.0'. This is your time to shine!
If you haven't even begun to prepare for 4.0, there are some really quick wins
you can use to your advantage.
## Menu Icon in the Launcher
The new launcher in 4.0 allows developers to provide a custom icon for
their watchapps and watchfaces.
<div class="pebble-dual-image">
<div class="panel">
{% markdown %}
![Launcher Icon](/images/blog/2016-08-19-pikachu-icon.png)
{% endmarkdown %}
</div>
<div class="panel">
{% markdown %}
![Launcher >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-08-19-pikachu-launcher.png)
{% endmarkdown %}
</div>
</div>
> If your `png` file is color, we will use the luminance of the image to add
> some subtle gray when rendering it in the launcher, rather than just black
> and white. Transparency will be preserved.
You should add a 25x25 `png` to the `resources.media` section of the
`package.json` file, and set `"menuIcon": true`.
Please note that icons that are larger will be ignored and
your app will have the default icon instead.
```js
"resources": {
"media": [
{
"menuIcon": true,
"type": "png",
"name": "IMAGE_MENU_ICON",
"file": "images/icon.png"
}
]
}
```
## Timeline Quick View
This new feature really brings your timeline into the present, with your
next appointment appearing as a modal notification over the active watchface.
We introduced the ``UnobstructedArea`` API in 4.0 to allow developers to detect
if the screen is being obstructed by a system modal. Below you can see two
examples of the Simplicity watchface. The Pebble on the left is not using the
``UnobstructedArea`` API, and the Pebble on the right is.
<div class="pebble-dual-image">
<div class="panel">
{% markdown %}
![Simplicity >{pebble-screenshot,pebble-screenshot--time-white}](/images/blog/2016-08-19-simplicity-std.png)
{% endmarkdown %}
<p class="blog__image-text">Watchface not updated</p>
</div>
<div class="panel">
{% markdown %}
![Simplicity >{pebble-screenshot,pebble-screenshot--time-white}](/images/blog/2016-08-19-simplicity-qv.png)
{% endmarkdown %}
<p class="blog__image-text">Respects new 4.0 unobstructed area</p>
</div>
</div>
You can detect changes to the available screen real-estate and then move, scale,
or hide their layers to achieve an optimal layout while the screen is partially
obscured. This provides a fantastic experience to your users.
There's {% guide_link user-interfaces/unobstructed-area "a dedicated guide" %}
which goes into more detail about the ``UnobstructedArea`` API, it contains
examples and links to sample implementations.
## AppGlances
While your static app icon and app title from the `package.json` are being used as the
default to present your app to the user,
_AppGlances_ allow developers to control this content at runtime and
to provide meaningful feedback to users directly from the new launcher.
The API exposes the ability to dynamically change
the `icon` and `subtitle_template_string` text of your application in the
launcher.
<img src="/assets/images/blog/2016-05-24-kickstarter-3/launcher.gif"
alt="Updated Launcher" class="pebble-screenshot pebble-screenshot--time-black">
<p class="blog__image-text">Preview version of 4.0 launcher</p>
Utilizing the ``App Glance`` API doesn't need to be difficult. We've provided
guides, examples and sample applications for using the
{% guide_link user-interfaces/appglance-c "AppGlance C API" %} and the
{% guide_link user-interfaces/appglance-pebblekit-js "AppGlance PebbleKit JS API" %}.
## The Diorite Platform
The Diorite platform was created for the new Pebble 2 devices. Its display is
rectangular, 144x168 pixels, with 2 colors (black and white). It has a
microphone and heart rate monitor, but it doesn't have a compass. If your app
works with Aplite it will already work with Diorite, but there are some important considerations to note:
* If you chose to limit your app to some platforms, you need to add `"diorite"` to your `targetPlatforms` in the `package.json`.
* Check for the appropriate usage of
{% guide_link best-practices/building-for-every-pebble#available-defines-and-macros "compiler directives" %}.
In general, it's better to use capability and feature detection, rather than platform
detection. For example, when dealing with resources, use the suffix `~bw` instead of `~aplite` so they are picked on all black and white platforms.
## What's Next
We're really excited for the release of Pebble OS 4.0 and the new features it
brings. It's now time for you to take advantage of the new APIs and
enhance your existing projects, or even create entirely new ones!
Why not build something like the
{% guide_link design-and-interaction/one-click-actions "One Click Action" %}
application, which utilizes the new ``App Glance`` and ``AppExitReason`` APIs.
Please remember that we will promote watchfaces and watchapps
that make use of these new 4.0 APIs if you
submit them to the appstore **from August 31st onwards**.
Let us know on [Twitter](https://twitter.com/pebbledev) if you build something
cool using the new APIs! We'd love to hear about your experiences with the SDK.
Happy Hacking!
Team Pebble

View file

@ -0,0 +1,110 @@
---
# 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: Announcing Pebble SDK 4.0
author: jonb
tags:
- Freshly Baked
---
Today we have the pleasure of announcing the release of Pebble SDK 4.0. We have
published an updated version of the Pebble Tool, the SDK itself, and we've
deployed 4.0 onto [CloudPebble]({{ site.links.cloudpebble }}). It's time to get
busy!
## What's New
Pebble OS 4.0 has been released and users with Pebble Time, Pebble Time Steel
and Pebble Time Round will be receiving the update today.
We covered the new features in detail
[very recently](/blog/2016/08/19/prime-time-is-approaching-for-os-4.0/), but
let's have a quick reminder of the new APIs and functionality that's included.
### Rocky.js
Javascript on the freakin' watch! Although still in beta,
[Rocky.js](/docs/rockyjs/) lets you start developing watchfaces in JavaScript
using standard Web APIs, which you can then run directly on your watch. It's an
embedded JavaScript revolution!
Read the updated
[Rocky.js blog post](/blog/2016/08/15/introducing-rockyjs-watchfaces/)
to get started.
### Timeline Quick View
Timeline Quick View displays upcoming information from your timeline on your
watchface. We introduced the ``UnobstructedArea`` API to allow developers to
detect if the screen is being obstructed by a system modal, such as Timeline
Quick View. Developers can use this new API to adapt their watchface layout
according to the available screen real estate.
Read the {% guide_link user-interfaces/unobstructed-area "UnobstructedArea guide" %}
to get started.
### AppGlances
With the new ``AppGlance`` API, developers can dynamically change the icon and
subtitle of their watchapp within the system launcher, at runtime. This allows
developers to provide meaningful feedback to users without the need for their
application to be launched.
Read the
{% guide_link user-interfaces/appglance-c "AppGlance C guide" %} and the
{% guide_link user-interfaces/appglance-pebblekit-js "AppGlance PebbleKit JS guide" %}
to get started.
### Diorite Platform
The new Pebble 2 devices use apps built for the Diorite platform, so you'll need
SDK 4.0 to develop applications which target those devices.
Take a look at the
{% guide_link tools-and-resources/hardware-information "Hardware Information guide" %}
to find out about the capabilities of the Pebble 2.
### One Click Action application
The One Click Action application pattern promotes a type of watchapp which
serves a single purpose. It launches, performs an action, and then terminates.
This pattern utilizes the new ``AppGlance`` and ``AppExitReason`` APIs.
Take a look at the
{% guide_link design-and-interaction/one-click-actions "One Click Actions guide" %}
to get started.
## Dude, Where's my HRM API?
We had planned on shipping the Heart Rate API with 4.0, but it's been pushed
back into 4.1 so that we can add even more awesomeness. Pebble 2 devices will
begin to appear on wrists after firmware 4.1 ships, so you'll still have time to
begin implementing HRM data into your watchapps and watchfaces. We will announce
details of the HRM API as soon as it's available.
## What's Next
Please remember that we will promote watchfaces and watchapps that make use of
these new 4.0 APIs if you submit them to the appstore **from August 31st
onwards**.
Let us know on [Twitter]({{ site.links.twitter }}) if you build something
cool using the new APIs! We'd love to hear about your experiences with the SDK.
Happy Hacking!
Team Pebble

View file

@ -0,0 +1,119 @@
---
# 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: Timeline - The Future of the Past!
author: jonb
tags:
- Freshly Baked
---
If youve been living under a rock, or have just been sunning yourself on the
beach for the past week, you might have missed the
[launch of Pebble OS 4.0](/blog/2016/08/30/announcing-pebble-sdk4/). This new
version introduced some fantastic new features, such as the
[updated health app](https://blog.getpebble.com/2016/08/30/fw4-0/),
{% guide_link user-interfaces/unobstructed-area "timeline quick view" %},
{% guide_link user-interfaces/appglance-c "app glances" %},
[Rocky.js](/blog/2016/08/15/introducing-rockyjs-watchfaces/) and a new
[system launcher](/blog/2016/08/19/prime-time-is-approaching-for-os-4.0/#menu-icon-in-the-launcher).
### The Past and the Furious
However, there was one change which was met with mixed feedback from both users
and developers alike, the removal of timeline past. Previously accessible via
the UP button, timeline past was removed as part of the new 4.0 user
experience (UX). In 4.0 we introduced new APIs to give developers more options
to improve their applications UX and potentially shift away from using the past
for interactions
Unfortunately, this change prevented users from accessing any timeline pin which
wasnt in the present or future, negatively affecting a number of apps and their
use cases for the timeline.
We carefully listened to feedback and suggestions from our developer community
via the [forums](https://forums.pebble.com),
[Reddit](https://www.reddit.com/r/pebble),
[Twitter](https://twitter.com/pebbledev) and
[Discord]({{ site.links.discord_invite }}), and we are happy to announce that timeline past
has returned in the v4.0.1 update. Users who need to access the timeline past
can now assign it to one of their quick launch buttons.
![Quick Launch >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-09-06-quick-launch.gif)
And so, with the reintroduction of timeline past, balanced was restored, and
that was the end of the story. Or was it?
### Back to the Future!
If youre the developer of an application which relies upon timeline past, you
will probably want to inform your users about how they can access timeline past,
as it will not be enabled by default. Fortunately, there are multiple ways in
which you can do this easily.
#### 1. App Store Description
Use the app store description of your app to explain that your application
utilizes timeline past and that users will need to assign it to quick launch.
For example:
> This application utilizes pins in the timeline past. On your Pebble, go to
> Settings, Quick Launch, Up Button, then select Timeline Past. You can
> then access timeline past by long pressing UP.
#### 2. Display a Splash Screen
Add a splash screen to your application which only runs once, and display a
message informing users how to enable timeline past. You could use the
[about-window](https://www.npmjs.com/package/@smallstoneapps/about-window)
Pebble package for a really quick and easy solution.
![About Window >{pebble-screenshot,pebble-screenshot--time-white}](/images/blog/2016-09-06-about-window.png)
#### 3. Display a One-Time Notification
Display a
[system notification](https://developer.pebble.com/guides/communication/using-pebblekit-js/#showing-a-notification)
which only fires once, and display a message informing users how to enable
timeline past.
![System Notification >{pebble-screenshot,pebble-screenshot--time-black}](/images/blog/2016-09-06-system-notification.png)
#### 4. Display a Timeline Notification
Display a
[timeline notification](https://developer.pebble.com/guides/pebble-timeline/),
and display a message informing users how to enable timeline past.
![Timeline Notification >{pebble-screenshot,pebble-screenshot--time-red}](/images/blog/2016-09-06-timeline-notification.png)
### The Future of the Past
For now, developers can continue to utilize timeline past, but over time we
would like to provide a more diverse set of functionality that allows developers
to surface information to their users. For example, some use cases of timeline
past may be more appropriate as using an app glance, or timeline quick view
instead.
### Were Listening!
Your feedback is incredibly important to us, its how we keep making awesome
things. We love to receive your product and feature
[suggestions](http://pbl.io/ideas) too.
Were particularly interested to hear about your use cases and ideas for
timeline as we travel further into the future! Let us know via
[the forums](https://forums.pebble.com),
[Twitter](https://twitter.com/pebbledev) and [Discord]({{ site.links.discord_invite }})!

View file

@ -0,0 +1,155 @@
---
# 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: 4.2-beta4 SDK - Emery Edition!
author: jonb
tags:
- Freshly Baked
---
We're incredibly excited to announce the first public beta of the Pebble SDK
4.2. This is the first time that developers can experience the new 'Emery'
platform which is specifically created for the upcoming
[Pebble Time 2](https://www.pebble.com/buy-pebble-time-2-smartwatch).
![Pebble Time 2](/images/blog/2016-10-11-intro.jpg)
## All About Those Pixels
The new display on the *Pebble Time 2* is almost 53% physically larger, and the
[Pixels per inch](https://en.wikipedia.org/wiki/Pixel_density) (PPI) has been
increased from 182 PPI to 202 PPI. This is a massive 200x228 pixels, compared to
144x168 on the *Pebble Time Steel*, that's an 88% increase in the total amount
of pixels!.
Take a look at our
{% guide_link tools-and-resources/hardware-information "Hardware guide" %} for
further information about the specifications of the Pebble Time 2.
Watchfaces and watchapps that have not been updated for the Emery platform will
run in *Bezel Mode*. This means they will appear centered on screen at their
original resolution (144x168), but due to the change in PPI, they appear
slightly smaller.
![Pebble Time 2 Bezel](/images/blog/2016-10-11-bezel.png)
<p class="blog__image-text">Left: Pebble Time Steel, Middle: Pebble Time 2 -
Bezel Mode, Right: Optimized for Pebble Time 2.<br />
<a href="https://github.com/pebble-examples/concentricity">Concentricity
Watchface</a></p>
The increased number of pixels would immediately make you think that you can
just add more elements to your watchface design, but due to the increase in PPI,
existing elements and fonts may feel slightly smaller than expected when viewed
on an actual device.
The image below simulates how the PPI difference makes a bezel mode application
feel smaller on the Pebble Time 2.
![Pebble Time 2 PPI](/images/blog/2016-10-11-dpi-comparison.png)
<p class="blog__image-text">Left: Pebble Time Steel, Right: Pebble Time 2 - Bezel Mode.</p>
We've now seen that the increased amount of pixels doesn't necessarily equate to
bigger text on our devices, due to the change in PPI, that's why we've created
the new [ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
API.
## Introducing the ContentSize API
All existing Pebble smartwatches provide the ability for users to change the
size of text for notifications and some system UI components using *Settings >
Notifications > Text Size*.
The new
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
API now exposes this setting to developers, in order for
them to adapt their application design and layout based on this user preference.
In the previous section, we saw how the PPI increase on Pebble Time 2 affected
the relative size of a watchface. In the image below you can see precisely how
text sizes are affected by the PPI change.
![Pebble Time 2 ContentSize](/images/blog/2016-10-11-contentsize.png)
<p class="blog__image-text">Yellow: Pebble Time Steel, Green: Pebble Time 2.</p>
To negate the effects of this reduced font size, the Emery platform uses larger
fonts by default. Developers can use the
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
API to match this
behaviour within their own applications.
The following table explains how the text size setting affects the content size
on each platform:
Platform | Text Size: Small | Text Size: Medium | Text Size: Large
---------|------------------|-------------------|-----------------
Aplite, Basalt, Chalk, Diorite | ContentSize: Small | ContentSize: Medium | ContentSize: Large
Emery | ContentSize: Medium | ContentSize: Large | ContentSize: Extra Large
Developers should aim to implement designs which adapt to the capabilities of
the device, but also the accessibility requirements of the user. The
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
API satisfies both of these goals.
For more information, read the
{% guide_link user-interfaces/content-size "ContentSize guide" %}.
## New for Rocky.js
With the release of SDK 4.2, we're extremely proud to announce that
[Rocky.js](/docs/rockyjs/) watchfaces can be published into the Pebble appstore!
As usual, don't publish apps with the beta SDK into the appstore, please wait
for the final release.
In addition to finalizing the memory contract for Rocky.js apps, we've also
added a few new APIs to work with:
### Memory Pressure
The `memorypressure` event has been added to allow developers to handle the
situations which occur when the amount of memory available to their application
has changed. Initially we've only implemented the `high` memory pressure level,
which occurs right before your application is about to be terminated, due to a
lack of memory. If you can free sufficient memory within the callback for this
event, your application will continue to run. Please refer to the Rocky.js
[documentation](/docs/rockyjs/rocky/#RockyMemoryPressureCallback) and the memory
pressure [example application](https://github.com/pebble-examples/rocky-memorypressure).
### UserPreferences
The first watchface setting exposed in the new `UserPreferences` object is
`contentSize`. This exposes the new
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
API to Rocky.js watchface
developers. Find out more in the Rocky.js
[UserPreferences documentation](/docs/rockyjs/rocky/#userPreferences).
## What's Next
Check out the [release notes](/sdk/changelogs/4.2-beta4/) for a full list of
changes and fixes that were included in SDK 4.2-beta4.
Let us know on [Twitter]({{ site.links.twitter }}) if you built something
cool using the new APIs! We'd love to hear about your experiences with the SDK.
Happy Hacking!
Team Pebble

View file

@ -0,0 +1,180 @@
---
# 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: Announcing Pebble SDK 4.3
author: jonb
tags:
- Freshly Baked
---
Things have been a bit 'quiet' recently, but we're back with another fresh
Pebble SDK! In this release we've included one of the most frequently requested
APIs, exposed the raw HRM sensor value, released PebbleKit 4.0, plus added an
exciting new BLE HRM mode.
## It's. Oh. So quiet. Ssshhhhhh.
We've added an often requested method for developers to detect if *Quiet Time*
is enabled. To say that we had a lot of requests for this would be an
understatement.
*Quiet Time* can be enabled manually, via calendar events, or via scheduled
times, so we've made a simple method for querying whether it's currently active.
All you need to do is check each minute if it's active. This is easily done
within the tick event, as follows:
```c
static void handle_tick(struct tm *tick_time, TimeUnits units_changed) {
if (units_changed & MINUTE_UNIT) {
if (quiet_time_is_active()) {
// It's nice and quiet
} else {
// Starts another big riot
}
}
}
```
You may also peek this value, for example, to prevent your application from
vibrating during *Quiet Time*:
```c
static void do_vibration() {
if (!quiet_time_is_active()) {
vibes_short_pulse();
}
}
```
## Raw HRM BPM
The Pebble [Health API](``HealthService``) now exposes the raw BPM value from
the Heart Rate Monitor sensor. This raw value is not filtered and is useful for
applications which need to display the real-time sensor value, similar to the
Pebble Workout app.
For example, in order to peek the current real-time HRM sensor, you would do the
following:
```c
HealthServiceAccessibilityMask hr = health_service_metric_accessible(HealthMetricHeartRateRawBPM, time(NULL), time(NULL));
if (hr & HealthServiceAccessibilityMaskAvailable) {
HealthValue val = health_service_peek_current_value(HealthMetricHeartRateRawBPM);
if(val > 0) {
// Display raw HRM 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);
}
}
```
Find out more in the
{% guide_link events-and-services/hrm "Heart Rate Monitor API guide" %}.
## PebbleKit 4.0
PebbleKit for [iOS](https://github.com/pebble/pebble-ios-sdk/) and
[Android](https://github.com/pebble/pebble-android-sdk/) facilitates
communication between Pebble watchapps and 3rd party companion phone apps.
Version 4.0 introduces a number of new features and bug fixes, including:
* Support for Pebble 2 *(required for iOS only)*
* Removal of Bluetooth Classic (iOS)
* Sports API - Support 3rd party HRM
* Sports API - Custom data field and label
* Sports API - Helper object to simplify usage, and minimize updates via
Bluetooth
For further information about the specific platform changes, view the full
[iOS](https://github.com/pebble/pebble-ios-sdk/), or
[Android](https://github.com/pebble/pebble-android-sdk/) changelogs.
## BLE HRM Mode
Ever wanted to use your *Pebble 2 HRM* as a dedicated BLE HRM device with your
favourite mobile fitness app? Well now you can! Firmware 4.3 now implements the
standard
[Bluetooth Heart Rate Service profile](https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=239866).
In order to enable this profile, users must enable 'Pebble Health', then enable
'Heart Rate Monitoring' within the `Pebble Health` settings within the Pebble
mobile app.
Developers who are looking to integrate directly with this profile should be
aware of the following:
The Heart Rate Service UUID will be present in the advertising payload of
Pebble, but you must open the Bluetooth settings on the Pebble to make it
advertise and be discoverable over Bluetooth.
Because it's highly likely that the Pebble is already connected to the phone, it
will not be advertising. Therefore, we recommend that your mobile app also
enumerates already connected devices, to see if any of them support the Heart
Rate Service. This is in addition to scanning for advertisements to find new
devices that support HRS. By enumerating connected devices, you improve the user
experience: users will not have to go into the Bluetooth settings menu on Pebble
if their Pebble is already connected to the phone.
The first time an application subscribes to the Heart Rate Measurement
characteristic, a UI prompt will appear on the Pebble, asking the user to allow
sharing of their heart rate data. This permission is stored for the duration of
the Bluetooth connection.
When HR data sharing is active, the HR sensor will run continuously at ~1Hz
sample rate. This means there is a significant battery impact when using this
feature for an extended period of time.
When all applications have unsubscribed from the Heart Rate Measurement
characteristic, the HR sensor automatically returns to its default state.
Mobile apps should unsubscribe from the Heart Rate Measurement characteristic as
soon as the data is no longer required. For example, a workout app should
unsubscribe when the workout has finished and/or the application is exited.
If the Heart Rate Service is used continuously for a prolonged period of time
(currently 4hrs), a notification is presented on the watch to remind the user
that the HR data is still being shared.
The user can stop sharing HRM data from *Settings > Bluetooth > Device*. If the
user chooses to stop sharing, the Bluetooth connection is briefly disconnected
and reconnected to forcefully remove all subscribers. Unfortunately the
Bluetooth GATT specification does not provide a better mechanism for a service
to unsubscribe subscribers, only subscribers can unsubscribe themselves.
Service Characteristics Notes:
* 'Sensor Contact' field is used.
* 'Body Sensor' characteristic is used. The value is constant though (It will
read "Wrist" / 0x02)
* 'RR Interval' is currently NOT used.
* 'Energy Expended' field is currently NOT used.
* 'Heart Rate Control Point' characteristic is currently NOT used.
## What's Next
Check out the [release notes](/sdk/changelogs/4.3/) for a full list of
changes and fixes that were included in SDK 4.3.
Let us know on [Twitter]({{ site.links.twitter }}) if you built something
cool using the new APIs! We'd love to hear about your experiences with the SDK.
Happy Hacking!
Team Pebble

View file

@ -0,0 +1,145 @@
---
# 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.js - Pebble Package Edition!
author: jonb
tags:
- Freshly Baked
---
We're pleased to announce that [Pebble.js](https://pebble.github.io/pebblejs/)
has now been [published](https://www.npmjs.com/package/pebblejs) as a
{% guide_link pebble-packages "Pebble Package" %}. Pebble.js lets developers
easily create Pebble applications using JavaScript by executing the JavaScript
code within the mobile application on a user's phone, rather than on the watch.
![Pebble.js as a Pebble Package](/images/blog/2016-12-22-pebble-js.jpg)
Making Pebble.js a Pebble Package means Pebble.js projects can be converted to
standard Pebble C projects. This gives benefits like the ability to
easily utilize other Pebble Packages, such as
[Clay for Pebble](https://www.npmjs.com/package/pebble-clay), or easily
importing and exporting the project with
[CloudPebble]({{ site.links.cloudpebble }}).
The Pebble.js package is using the
[`develop`](https://github.com/pebble/pebblejs/tree/develop) branch from the
[Pebble.js repository](https://github.com/pebble/pebblejs) on Github, and
can be updated independently from CloudPebble deployments.
**It also supports the Diorite platform!**.
## Creating a New Project
The initial steps vary if you're using CloudPebble or the Local SDK. Follow the
appropriate steps below to create a new project.
#### CloudPebble
If you're using CloudPebble, follow these initial steps:
1. Create a new project:
* Project Type = Pebble C SDK
* Template = Empty Project
2. Add the following dependency:
* Package Name = pebblejs
* Version = 1.0.0
3. Add a new `main.c` file and an `index.js` file.
Now continue to add the [default project files](#default-project-files).
#### Local SDK
If you're using the Local SDK, just create a new C project with Javascript
support:
```nc|text
$ pebble new-project PROJECTNAME --javascript
```
Now continue to add the [default project files](#default-project-files).
#### Default Project Files
Copy and paste these default project files into your project, replacing any
existing file contents:
**your-main.c**
```c
#include <pebble.h>
#include "pebblejs/simply.h"
int main(void) {
Simply *simply = simply_create();
app_event_loop();
simply_destroy(simply);
}
```
**index.js**
```javascript
require('pebblejs');
var UI = require('pebblejs/ui');
var card = new UI.Card({
title: 'Hello World',
body: 'This is your first Pebble app!',
scrollable: true
});
card.show();
```
At this point you should be able to compile and run your new project.
## Migrating an Existing Project
Unfortunately there isn't an automated way to migrate your existing Pebble.js
project, but the steps are fairly straightforward.
1. Create a new project, following the [steps above](#creating-a-new-project).
2. Change the project settings to match your old project settings, including the
UUID.
3. Copy your project resources (images, fonts etc.), and source files into the
new project.
4. Compile and enjoy your new C project with Pebble.js support.
> Note: `index.js` is a direct replacement for `app.js`, which may be your old
Javascript file.
## Next Steps?
Want to add Clay support to your project? It's now easy by following the
standard Clay [Getting Started](https://github.com/pebble/clay#clay)
instructions!
If you have any questions or problems, post the details on the
[developer forum](https://forums.pebble.com/t/pebble-js-pebble-package-edition/27315)
or [Discord](http://discord.gg/aRUAYFN).
Happy Holidays!
Jon Barlow + Team #PEBBLExFITBIT