mirror of
https://github.com/google/pebble.git
synced 2025-06-04 01:03:12 +00:00
Import the pebble dev site into devsite/
This commit is contained in:
parent
3b92768480
commit
527858cf4c
1359 changed files with 265431 additions and 0 deletions
120
devsite/source/_posts/2013-07-24-Using-Pebble-System-Fonts.md
Normal file
120
devsite/source/_posts/2013-07-24-Using-Pebble-System-Fonts.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
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).
|
|
@ -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!
|
|
@ -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 ;)
|
|
@ -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, it’s 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 shouldn’t be allowed to accidentally interact
|
||||
with each other. However, there doesn’t 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 don’t 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 aren’t 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 app’s 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, don’t hesitate to [contact us](/contact).
|
333
devsite/source/_posts/2014-06-10-Pebble-SDK-Tutorial-1.md
Normal file
333
devsite/source/_posts/2014-06-10-Pebble-SDK-Tutorial-1.md
Normal 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:
|
||||
|
||||

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

|
||||
|
||||
###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).
|
237
devsite/source/_posts/2014-06-18-Your-First-Watchface.md
Normal file
237
devsite/source/_posts/2014-06-18-Your-First-Watchface.md
Normal 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:
|
||||
|
||||

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

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

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

|
||||
|
||||
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).
|
||||
|
||||
|
|
@ -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).
|
|
@ -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, don’t hesitate to [contact us](/contact).
|
149
devsite/source/_posts/2014-10-29-Displaying-remote-images.md
Normal file
149
devsite/source/_posts/2014-10-29-Displaying-remote-images.md
Normal 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
|
|
@ -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.
|
||||
|
||||

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

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

|
||||
|
||||
## 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
|
|
@ -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
|
||||
wouldn’t 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 0’s to 1’s (an actual device can only change 1’s to 0’s
|
||||
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 don’t 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/
|
|
@ -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
|
||||
[Google’s 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 phone’s 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 user’s approximate
|
||||
location. In practice, this works reasonably well as long as the emulator is
|
||||
actually running on the user’s computer. However, when the emulator is *not*
|
||||
running on the user’s computer (e.g. when using CloudPebble), the result isn’t
|
||||
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 app’s 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
|
||||
app’s 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 user’s
|
||||
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
|
||||
CloudPebble’s 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 VNC’s Remote Framebuffer Protocol. QEMU has a VNC server built-
|
||||
in, making this the obvious choice to handle displaying the screen.
|
||||
|
||||
CloudPebble’s 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. CloudPebble’s 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 user’s 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 developer’s intended
|
||||
URL. CloudPebble then inserts a return_to parameter and opens a new window with
|
||||
the developer’s 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
|
239
devsite/source/_posts/2015-02-13-Bezier-Curves-And-GPaths.md
Normal file
239
devsite/source/_posts/2015-02-13-Bezier-Curves-And-GPaths.md
Normal 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:
|
||||
|
||||

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

|
||||
|
||||
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!
|
||||
|
||||

|
||||
|
||||
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!
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
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 CloudPebble’s 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 
|
||||
* 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. 
|
||||
* 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. 
|
||||
* 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. 
|
||||
* 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. 
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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 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 you’d 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!
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
|
||||
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.
|
|
@ -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).
|
99
devsite/source/_posts/2015-10-29-ios-migration.md
Normal file
99
devsite/source/_posts/2015-10-29-ios-migration.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
### 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!
|
||||
|
|
@ -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. It’s also intuitive and simple for users to interact with.
|
||||
Here’s 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 watchapp’s
|
||||
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.
|
||||
|
||||

|
||||
|
||||
|
||||
## But How Does It Actually Work?
|
||||
|
||||
Let’s take a closer look at what’s happening behind the scenes to see what’s
|
||||
really going on.
|
||||
|
||||

|
||||
|
||||
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 it’s 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 user’s 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 there’s a lot going on, but let’s 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, don’t forget to destroy the session:
|
||||
|
||||
```c
|
||||
dictation_session_destroy(s_dictation_session);
|
||||
```
|
||||
|
||||
|
||||
## Voice-enabled Watchapps
|
||||
|
||||
Here’s 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. “Don’t 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 you’ve 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 you’re 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 }})?
|
117
devsite/source/_posts/2015-12-01-A-New-Pebble-Tool.md
Normal file
117
devsite/source/_posts/2015-12-01-A-New-Pebble-Tool.md
Normal 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 }})!
|
163
devsite/source/_posts/2015-12-02-Bitmap-Resources.md
Normal file
163
devsite/source/_posts/2015-12-02-Bitmap-Resources.md
Normal 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/).
|
|
@ -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 doesn’t unpublish the previous one. You can still manually unpublish
|
||||
releases whenever they want.
|
||||
|
||||
* The appstore will provide the most recently compatible release of an app to
|
||||
users. This means that if you publish a new release that has 3.x Aplite
|
||||
support, the newest published release that supports 2.x Aplite will be
|
||||
provided to users on 2.x Aplite.
|
||||
|
||||
* There will be a fourth Asset Collection type that you can create: Legacy
|
||||
Aplite. Apps that have different UI designs between 2.x and 3.x on Aplite
|
||||
should use the Legacy Aplite asset collection for their 2.x assets.
|
||||
|
||||
|
||||
## Suggested Changes
|
||||
|
||||
To fully migrate to SDK 3.x, we also suggest you make these 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 }}).
|
127
devsite/source/_posts/2016-01-29-Multiple-JavaScript-Files.md
Normal file
127
devsite/source/_posts/2016-01-29-Multiple-JavaScript-Files.md
Normal 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.
|
||||
|
|
@ -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 doesn’t allow you to run any JavaScript you previously couldn’t, 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 user’s location with the built in [geolocation.getCurrentPosition]
|
||||
[getcurrentposition] API, then fetches weather information for the returned
|
||||
position.
|
||||
|
||||
The initial version of the code we’re 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) {
|
||||
//...
|
||||
};
|
||||
//...
|
||||
};
|
||||
```
|
||||
|
||||
We’ll 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 we’ve moved all of the OWMWeather code into a class, we can safely remove
|
||||
the `owmWeather` prefixes on all of our methods and properties. While we’re 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
|
271
devsite/source/_posts/2016-02-29-introducing-app-debugging.md
Normal file
271
devsite/source/_posts/2016-02-29-introducing-app-debugging.md
Normal 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 = &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, &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 `×[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, &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, &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!
|
|
@ -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 >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 don’t 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
|
||||
|
||||

|
||||
<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, you’re 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 we’ll only be drawing circular progress bars on all
|
||||
platforms.
|
||||
|
||||
## Step 1 - Building the basic watchface
|
||||
|
||||

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

|
||||
|
||||
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 you’re 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
|
||||
|
||||

|
||||
|
||||
We’re going to create 2 new layers, one for the grey progress indicator dots
|
||||
and the other for the progress bar. To simplify the example, we’re 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, we’re 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
|
||||
|
||||

|
||||
|
||||
The final thing we’re going to add is an indicator to show your average daily
|
||||
steps for this time of day. We’ve already calculated the average, and we’re
|
||||
going to use [`graphics_fill_radial()`]
|
||||
(https://developer.pebble.com/docs/c/Graphics/Drawing_Primitives/#graphics_fill_radial)
|
||||
again but this time it’s just to draw a yellow line. We’ll 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
|
||||
|
||||

|
||||
<p style="text-align: center; font-size: 0.9em;">Strider watchface</p>
|
||||
|
||||
## Final thoughts
|
||||
|
||||
I’ve really only scratched the surface of the HealthService API, but hopefully
|
||||
you’re now sufficiently excited to build something awesome! We’d really love to
|
||||
see how you use it, and If you create a health enabled watchapp or watchface,
|
||||
don’t forget to let us know on [Twitter](http://twitter.com/pebbledev) or on
|
||||
[Discord]({{ site.links.discord_invite }})!
|
149
devsite/source/_posts/2016-05-16-Pebble-JS-Round.md
Normal file
149
devsite/source/_posts/2016-05-16-Pebble-JS-Round.md
Normal 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.
|
||||
|
||||
|
||||
|
||||

|
||||
<p class="blog__image-text">Pebble.js running on all available platforms.</p>
|
||||
|
||||
Whether or not you’re 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.
|
||||
|
||||

|
||||
<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, we’re 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).
|
||||
|
||||
Here’s to more exciting weekend coding sessions in the future for both of us!
|
74
devsite/source/_posts/2016-06-07-pebble-packages.md
Normal file
74
devsite/source/_posts/2016-06-07-pebble-packages.md
Normal 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!
|
186
devsite/source/_posts/2016-06-15-sdk4-preview.md
Normal file
186
devsite/source/_posts/2016-06-15-sdk4-preview.md
Normal 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 - we’ve 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 we’ll 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
|
||||
|
||||
Let’s 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).
|
||||
|
||||

|
||||
|
||||
`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 user’s 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
|
||||
watchface’s 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 haven’t 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.
|
||||
|
||||

|
||||
|
||||
To help get you started thinking about and designing One Click Action apps,
|
||||
we’ve 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 we’re 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, we’ve 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.
|
||||
|
||||
## What’s Next
|
||||
|
||||
The SDK 4 developer preview is exactly that, a preview. You’ve probably noticed
|
||||
a few really important and exciting features we didn’t 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 Pebble’s fantastic new embedded
|
||||
JavaScript SDK, [Rocky.js](http://pebble.github.io/rockyjs/).
|
||||
|
||||
## Show Us What You Make
|
||||
|
||||
While we’ve 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 we’ve only really just begun to see
|
||||
what’s possible, and we’re more than excited to see what you’ll build with this
|
||||
new functionality.
|
||||
|
||||
Send us a link to something you’ve built on Twitter (
|
||||
[@pebbledev](https://twitter.com/pebbledev)) and we’ll look at including it in
|
||||
an upcoming newsletter (and send some swag your way).
|
||||
|
||||
Happy Hacking!
|
||||
|
||||
Team Pebble
|
117
devsite/source/_posts/2016-06-24-introducing-clay.md
Normal file
117
devsite/source/_posts/2016-06-24-introducing-clay.md
Normal 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.
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
## 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).
|
|
@ -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);
|
||||
});
|
||||
```
|
||||
|
||||

|
||||
|
||||
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!
|
|
@ -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 %}
|
||||

|
||||
{% endmarkdown %}
|
||||
</div>
|
||||
<div class="panel">
|
||||
{% markdown %}
|
||||

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

|
||||
{% endmarkdown %}
|
||||
<p class="blog__image-text">Watchface not updated</p>
|
||||
</div>
|
||||
<div class="panel">
|
||||
{% markdown %}
|
||||

|
||||
{% 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
|
||||
|
||||
|
110
devsite/source/_posts/2016-08-30-announcing-pebble-sdk4.md
Normal file
110
devsite/source/_posts/2016-08-30-announcing-pebble-sdk4.md
Normal 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
|
|
@ -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 you’ve 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 application’s UX and potentially shift away from using the past
|
||||
for interactions
|
||||
|
||||
Unfortunately, this change prevented users from accessing any timeline pin which
|
||||
wasn’t 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.
|
||||
|
||||

|
||||
|
||||
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 you’re 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.
|
||||
|
||||

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

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

|
||||
|
||||
### 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.
|
||||
|
||||
### We’re Listening!
|
||||
|
||||
Your feedback is incredibly important to us, it’s how we keep making awesome
|
||||
things. We love to receive your product and feature
|
||||
[suggestions](http://pbl.io/ideas) too.
|
||||
|
||||
We’re 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 }})!
|
155
devsite/source/_posts/2016-10-11-Emery-SDK-Beta.md
Normal file
155
devsite/source/_posts/2016-10-11-Emery-SDK-Beta.md
Normal 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).
|
||||
|
||||
|
||||

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

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

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

|
||||
<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
|
180
devsite/source/_posts/2016-11-23-Announcing-Pebble-SDK-4.3.md
Normal file
180
devsite/source/_posts/2016-11-23-Announcing-Pebble-SDK-4.3.md
Normal 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
|
145
devsite/source/_posts/2016-12-22-pebblejs-package.md
Normal file
145
devsite/source/_posts/2016-12-22-pebblejs-package.md
Normal 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.
|
||||
|
||||
|
||||

|
||||
|
||||
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
|
Loading…
Add table
Add a link
Reference in a new issue