SlideShare a Scribd company logo
2015 Pebble Developer Retreat
Creating Pebble Apps for Aplite, Basalt, and Chalk
Kevin Conley, Firmware Engineer
Why Target Multiple Platforms?
Maximize Userbase
Pebble Time
Pebble Classic
Continuity
1. User installs your app on an older
platform
2. User upgrades to a new Pebble
3. User already has the most recent
version of your app in their locker :)
Developer Portal Icons
Developer Portal Icons
Agenda
• Runtime Platforms vs. SDK Versions
• Colors
• Display Shapes
• SDK Compatibility
Runtime Platforms vs.
SDK Versions
“Runtime Platform”
• Set of runtime attributes and capabilities
• Does NOT refer to a single watch hardware model
Aplite
2.x ✅
Pebble Classic Pebble Steel
OS Compatibility
• 2.x is legacy
• 3.x for Aplite coming soon!
Aplite Basalt
2.x ✅
3.0 ❌ ✅
Pebble Time Pebble Time Steel
OS Compatibility
Aplite Basalt Chalk
2.x ✅
3.0 ❌ ✅
3.6 ❌ ✅
Pebble Time Round
OS Compatibility
Aplite Basalt Chalk
CPU
24k

ARM Cortex M3
64k
ARM Cortex M4
Resolution
144x168px

(Rectangular)
180x180px
(Circular)
Color Black & White 64 Colors
SmartStrap ❌ ✅
Mic ❌ ✅
Hardware Features
Aplite, Basalt, and Chalk are runtime platforms, not SDK versions.
Aplite Basalt Chalk
2.x ✅
3.0 ❌ ✅
3.6 ❌ ✅
3.x ✅
Unified 3.x SDK later this year
OS Compatibility
runs on
2.x 3.x
Aplite Basalt
2.x Aplite ✅ ✅
3.x Basalt ❌ ✅
compiledagainst
2.x Aplite runs
on 3.x Basalt
SDK Compatibility
runs on
2.x 3.x
Aplite Aplite Basalt Chalk
2.x Aplite ✅ ✅ ✅ ❌
3.x
Aplite ❌ ✅ ❌ ❌
Basalt ❌ ❌ ✅ ❌
Chalk ❌ ❌ ❌ ✅
compiledagainst
3.x apps
are compiled
per platform
SDK Compatibility
Colors
Colors
• Aplite
•Black and white
• Basalt and Chalk
•64 colors
•Pebble color picker tool:
• https://developer.getpebble.com/more/color-picker
•Download color palettes for Photoshop, GIMP, ImageMagick
• https://developer.getpebble.com/guides/pebble-apps/display-
and-animations/intro-to-colors/#color-palettes
Open-Source Dithering Library
• Created by Mathew Reiss
• Display approximate colors beyond provided color palette
• https://github.com/mathewreiss/dithering
Aplite: shades of gray Basalt/Chalk: > 300 colors
#define COLOR_FALLBACK(_____, _____)
#define COLOR_FALLBACK(color_value, bw_value)
#define COLOR_FALLBACK(color_value, bw_value)
(deprecated)
Organize Code Using Color Macros
#define PBL_IF_COLOR_ELSE(if_true, if_false)
#define PBL_IF_BW_ELSE(if_true, if_false)
Returns if_true on watches that support color and if_false otherwise
Returns if_true on watches that only support black & white and if_false otherwise
Organize Code Using Color Defines
#define PBL_COLOR
#define PBL_BW
Defined on watches that support color and undefined otherwise
Defined on watches that only support black & white and undefined otherwise
Color-Specific Resources
"resources": {

"media": [

{

"type": "png",

"name": "MY_IMAGE",

"file": "images/image.png"

}

]

}

appinfo.json: resources/
images/
image~bw.png
image~color.png
image~bw.png image~color.png
WatchInfoColor
GColor text_foreground_color, text_background_color;
switch(watch_info_get_color()) {

case WATCH_INFO_COLOR_TIME_STEEL_GOLD:

// White on red theme
text_foreground_color = GColorWhite;
text_background_color = GColorRed;

break;

case WATCH_INFO_COLOR_TIME_ROUND_BLACK_20:

// White on black theme

text_foreground_color = GColorWhite;
text_background_color = GColorBlack;

break;

/* Other cases... */ 

}
text_layer_set_text_color(s_label_layer, text_foreground_color);

text_layer_set_background_color(s_label_layer, text_background_color);
• Customize your app’s UI using the color of the Pebble watch
Display Shapes
Display Shapes
• Round has 7% more
visible pixels than
rectangular
Rectangular
(144 x 168)
Round (visible)
Round (total)
(180 x 180)
(0, 0)
Calculate Position/Size as a Function of Other Layers
const Layer *window_layer = window_get_root_layer(window);
const GRect text_frame = GRect(0, 72, 144, 20);
const TextLayer *text_layer = text_layer_create(text_frame);
text_layer_set_text(text_layer, "Hello world!");
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(text_layer));
Calculate Position/Size as a Function of Other Layers
const Layer *window_layer = window_get_root_layer(window);
const GRect text_frame = GRect(0, 72, 144, 20);
const TextLayer *text_layer = text_layer_create(text_frame);
text_layer_set_text(text_layer, "Hello world!");
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(text_layer));
Calculate Position/Size as a Function of Other Layers
const Layer *window_layer = window_get_root_layer(window);
const GRect text_frame = GRect(0, 72, 144, 20);
const TextLayer *text_layer = text_layer_create(text_frame);
text_layer_set_text(text_layer, "Hello world!");
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(text_layer));
Calculate Position/Size as a Function of Other Layers
const Layer *window_layer = window_get_root_layer(window);
const GRect window_bounds = layer_get_bounds(window_layer);
const int16_t text_height = 20;
const int16_t text_frame_y =
(window_bounds.size.h - text_height) / 2;
const GRect text_frame = (GRect) {
.origin = GPoint(0, text_frame_y),
.size = GSize(window_bounds.size.w, text_height)
};
const TextLayer *text_layer = text_layer_create(text_frame);
text_layer_set_text(text_layer, "Hello world!");
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(text_layer));
void grect_align(GRect *rect, const GRect *inside_rect,
const GAlign alignment, const bool clip);
GAlignCenter
clip = false
rect
inside_rect
Align Rectangles Within Other Rectangles Using grect_align()
inside_rect
GAlignTop
clip = true
rect
typedef enum GAlign {

GAlignCenter,

GAlignTopLeft,

GAlignTopRight,

GAlignTop,

GAlignLeft,

GAlignBottom,

GAlignRight,

GAlignBottomRight,

GAlignBottomLeft

} GAlign; GAlignBottomRight
GAlignRight
GAlignTopRight
GAlignBottom
GAlignCenter
GAlignTop
GAlignBottomLeft
GAlignLeft
GAlignTopLeft
const GRect a = grect_inset(rect, GEdgeInsets(16)); // all sides
const GRect b = grect_inset(rect, GEdgeInsets(25, 16)); // top & bottom, right & left
const GRect c = grect_inset(rect, GEdgeInsets(25, 16, 10)); // top, right & left, bottom
const GRect d = grect_inset(rect, GEdgeInsets(25, -20, 10, 16)); // top, right, bottom, left
a b
c d
16px
16px
25px 25px 25px
25px
10px 10px
16px 16px 16px 16px16px 16px 16px 20px
Inset Rectangles Using grect_inset() and GEdgeInsets()
rect
GPoint grect_center_point(const GRect *rect);
result
Find the Center of Rectangles Using grect_center_point()
• Use PBL_IF_[ROUND | RECT]_ELSE() macros to choose
values or simple expressions based on the display shape
• Similar to PBL_IF_[COLOR | BW]_ELSE()
Organize Code Using Display Shape Macros/Defines
const uint16_t area = PBL_IF_ROUND_ELSE(PI * (width * width / 4),
(width * height));
• For multiple lines, choose code using PBL_[ROUND | RECT] defines
#if defined(PBL_ROUND)

const GPoint center_point = grect_center_point(&rect);

graphics_fill_circle(ctx, center_point, radius);

#else
const uint16_t corner_radius = 0;

graphics_fill_rect(ctx, rect, corner_radius, GCornerNone);

#endif
Organize Code Using Display Shape Macros/Defines
• For > 5 lines, break code into functions with _rect/_round suffixes
• If the functions share the same arguments, you can simplify
calling them by using the PBL_IF_[ROUND | RECT]_ELSE() macro
• Otherwise, use the PBL_[ROUND | RECT] defines:
Organize Code Using Display Shape Macros/Defines
PBL_IF_RECT_ELSE(function_rect, function_round)(arg1, arg2, ...);
#if defined(PBL_RECT)

function_rect(arg1);

#else

function_round(arg1, arg2);

#endif
• For > 2 display-specific functions, break into separate _rect/_round files
• Use wrapper file to choose appropriate implementation using display
shape defines/macros
• Alternatively, if the files share a common API:
• (also requires ifdef’ing out implementations)
Organize Code Using Display Shape Macros/Defines
// main.c
#if defined(PBL_RECT)

#include “file_rect.h"

#else

#include “file_round.h"

#endif
src/
file_rect.c
file_rect.h
file_round.c
file_round.h
file.c
file.h
main.c
Display-Specific Resources
"resources": {

"media": [

{

"type": "png",

"name": "MY_IMAGE",

"file": "images/image.png"

}

]

}

resources/
images/
image~rect.png
image~color~round.png
appinfo.json:
image~rect.png image~color~round.png
• Most-specific resource will be used
• Ambiguity will result in compiler error
Two-Pixel Margin on Round Displays
• Extend background colors to all outer
edges
• Avoid thin rings around the edge of the
display
• Manufacturing variations may result in
off-center appearance
• Instead, use thick rings or significantly
inset from the edge
Platform-Specific Designs
“Caltrain” by Katharine Berry: https://github.com/Katharine/pebble-caltrain/
Rectangular Round
UI Components
Status Bar
• For SDK 2.x, the status bar appears by default and insets the window from
the top unless you use:
window_set_fullscreen(window, true);
• For SDK 3.x, the status bar is a UI component you must create, configure,
and add to the layer hierarchy yourself:
static StatusBarLayer *s_status_bar;

s_status_bar = status_bar_layer_create();

status_bar_layer_set_separator_mode(s_status_bar,
StatusBarLayerSeparatorModeDotted);

status_bar_layer_set_colors(s_status_bar, GColorBlack, GColorWhite);

const Layer *status_bar_layer = status_bar_layer_get_layer(s_status_bar);

layer_add_child(window_root_layer, status_bar_layer);
Status Bar
Rectangular Round
Status Bar
s_status_bar = status_bar_layer_create();

GRect status_bar_rect = window_bounds;

status_bar_rect.size.h = STATUS_BAR_LAYER_HEIGHT;

layer_set_frame(
status_bar_layer_get_layer(s_status_bar),

status_bar_rect);



// top/bottom: statusbar, left/right: 0
const GEdgeInsets insets = GEdgeInsets(
STATUS_BAR_LAYER_HEIGHT, 0);
GRect menu_layer_rect = grect_inset(
window_bounds, insets);
Status Bar
ActionBar
Rectangular
Round
ActionBar
s_action_bar = action_bar_layer_create();

action_bar_layer_add_to_window(
s_action_bar, window);



// top: statusbar, right: actionbar, bottom/left:0
const GEdgeInsets insets = GEdgeInsets(

STATUS_BAR_LAYER_HEIGHT, ACTION_BAR_WIDTH, 0, 0);

GRect content_rect = grect_inset(
window_bounds, insets);
ActionBar
MenuLayer
Rectangular Round
MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT
MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT

MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT
MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT
MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT
MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT
MENU_CELL_BASIC_CELL_HEIGHT
MENU_CELL_BASIC_CELL_HEIGHT
MENU_CELL_BASIC_CELL_HEIGHT
MenuLayer
// default on Chalk
menu_layer_set_center_focused(menu_layer, true);
static int16_t get_cell_height_callback(MenuLayer *menu_layer,
MenuIndex *cell_index,

void *callback_context) {

#if defined(PBL_ROUND)
// when center_focused, MenuLayer can handle cell heights
// that change based on the selection status
if (menu_layer_is_index_selected(menu_layer, cell_index)) {

return MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT;

} else {

return MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT;

}
#else
return MENU_CELL_BASIC_CELL_HEIGHT;
#endif

}

MenuLayer
TextLayer
• To maximize content visible on round displays, enable text
flow and paging with an optional inset (for padding):
text_layer_enable_screen_text_flow_and_paging(s_text_layer, 8);
TextLayer
• Text flow on a round display without pagination looks weird…
WatchInfo
• Inspect what watch the app is running on
• Useful e.g. if you want to align visual elements with buttons
• Next to different screen coordinates for different watches
GRect layer_frame = GRectZero;

switch(watch_info_get_model()) {

case WATCH_INFO_MODEL_PEBBLE_STEEL:

layer_frame = GRect(0, 64, 144, 44);

break;

case WATCH_INFO_MODEL_PEBBLE_TIME:

layer_frame = GRect(0, 58, 144, 56);

break;

/* Other cases */

}
SDK Compatibility
Platform Defines
• Feature/capability defines, like PBL_COLOR, PBL_ROUND,
etc. should be used in favor of these
• Exception: CPU/memory-specific code
#define PBL_PLATFORM_APLITE
#define PBL_PLATFORM_BASALT
#define PBL_PLATFORM_CHALK
Platform Specific Resources
• Can also use ~aplite, ~basalt, and ~chalk suffixes
• But overall, in most cases it’s best to use ~rect, ~round, ~bw, ~color suffixes instead
"resources": {

"media": [

{

"type": "png",

"name": "MY_IMAGE",

"file": “images/image.png”,
"targetPlatforms": [
"basalt"
]

}

]

}

GPoint shim_gpoint_from_polar(GRect rect, int32_t angle) {

#if defined(PBL_SDK_2)
const GPoint center = GPoint(rect.origin.x + (rect.size.w / 2),
rect.origin.y + (rect.size.h / 2));
const int16_t radius = MIN(rect.size.w, rect.size.h) / 2;

return GPoint((sin_lookup(angle) * radius / TRIG_MAX_RATIO) + center.x,

(-cos_lookup(angle) * radius / TRIG_MAX_RATIO) + center.y);

#else

return gpoint_from_polar(rect, GOvalScaleModeFitCircle, angle);

#endif

}
Shims
• Newer SDK 3.x functions do not exist in SDK 2.x
• You can create shims for them using PBL_SDK_2/3 defines:
PebbleKit JS API Availability
• Some PebbleKit JS APIs don’t exist on Pebble Classic mobile apps,
• Check if they are available and fail gracefully if necessary
if (Pebble.getActiveWatchInfo) {
// API is available; use it!
var info = Pebble.getActiveWatchInfo();
console.log('Pebble model: ' + info.model);
} else {
// Gracefully handle unavailable API
}
Check Return Values
• APIs may return NULL if:
• Unsupported by platform
• Permissions not given
• Phone disconnected
• Check the return value before using it and fail gracefully if necessary
const DictationSession *session = dictation_session_create(0, callback,
callback_context);
if (session) {
/* Success! */
dictation_session_start(session);
} else {
/* DictationSession is unsupported, need to fail gracefully here! */
}
Marcel: Friday 1 pm
Starting with Chalk, you
need to compile with
PebbleKit iOS 3.0
Since PebbleOS 3.0, you
need to compile with
PebbleKit Android 3.0
AndroidiOS
Mobile App Compatibility
Running Multiple Emulators at Once
• pebble install --emulator=aplite && pebble install --emulator=basalt &&
pebble install —-emulator=chalk
Automating Taking Screenshots
• pebble screenshot --emulator=aplite && pebble screenshot --emulator=basalt
&& pebble screenshot —-emulator=chalk
Recap
• Runtime Platforms vs. SDK Versions
• Colors
• Display Shapes
• SDK Compatibility
Questions?

More Related Content

PDF
Overlay Technique | Pebble Developer Retreat 2014
PDF
#PDR15 - Pebble Graphics
PDF
#PDR15 - waf, wscript and Your Pebble App
PDF
Introducing Pebble SDK 2.0
PDF
PyHEP 2018: Tools to bind to Python
PDF
PL-4048, Adapting languages for parallel processing on GPUs, by Neil Henning
PDF
CMake best practices
PDF
Bolt C++ Standard Template Libary for HSA by Ben Sanders, AMD
Overlay Technique | Pebble Developer Retreat 2014
#PDR15 - Pebble Graphics
#PDR15 - waf, wscript and Your Pebble App
Introducing Pebble SDK 2.0
PyHEP 2018: Tools to bind to Python
PL-4048, Adapting languages for parallel processing on GPUs, by Neil Henning
CMake best practices
Bolt C++ Standard Template Libary for HSA by Ben Sanders, AMD

What's hot (20)

PDF
PEARC17: Modernizing GooFit: A Case Study
PDF
IRIS-HEP Retreat: Boost-Histogram Roadmap
PPTX
Migrating from OpenGL to Vulkan
PDF
Simple ETL in python 3.5+ with Bonobo - PyParis 2017
PDF
The Power of Rails 2.3 Engines & Templates
PDF
Anatomy of a Gradle plugin
PDF
PDF
PyHEP 2019: Python 3.8
PDF
Modern OpenGL Usage: Using Vertex Buffer Objects Well
PPTX
How data rules the world: Telemetry in Battlefield Heroes
PDF
Developing and Deploying Apps with the Postgres FDW
PDF
Best Practices in Qt Quick/QML - Part 1 of 4
 
PDF
OpenGL NVIDIA Command-List: Approaching Zero Driver Overhead
PDF
Experiments in Sharing Java VM Technology with CRuby
PPTX
Running Ruby on Solaris (RubyKaigi 2015, 12/Dec/2015)
PDF
OrientDB and Hazelcast
PDF
CHEP 2018: A Python upgrade to the GooFit package for parallel fitting
PDF
PG-4034, Using OpenGL and DirectX for Heterogeneous Compute, by Karl Hillesland
PDF
Foreign Data Wrapper Enhancements
PEARC17: Modernizing GooFit: A Case Study
IRIS-HEP Retreat: Boost-Histogram Roadmap
Migrating from OpenGL to Vulkan
Simple ETL in python 3.5+ with Bonobo - PyParis 2017
The Power of Rails 2.3 Engines & Templates
Anatomy of a Gradle plugin
PyHEP 2019: Python 3.8
Modern OpenGL Usage: Using Vertex Buffer Objects Well
How data rules the world: Telemetry in Battlefield Heroes
Developing and Deploying Apps with the Postgres FDW
Best Practices in Qt Quick/QML - Part 1 of 4
 
OpenGL NVIDIA Command-List: Approaching Zero Driver Overhead
Experiments in Sharing Java VM Technology with CRuby
Running Ruby on Solaris (RubyKaigi 2015, 12/Dec/2015)
OrientDB and Hazelcast
CHEP 2018: A Python upgrade to the GooFit package for parallel fitting
PG-4034, Using OpenGL and DirectX for Heterogeneous Compute, by Karl Hillesland
Foreign Data Wrapper Enhancements
Ad

Similar to #PDR15 Creating Pebble Apps for Aplite, Basalt, and Chalk (20)

PDF
#PDR15 - Developing for Round
PPTX
pebble - Building apps on pebble
PDF
Pebble NYC Meetup Pebble Time Round and the Chalk SDK
PDF
Pebble Watch iOS SDK Overview
PPT
Some examples of shapes are Lines, Polygons, Axes, and Text
KEY
Processing presentation
PDF
Announcing Pebble SDK 2.0
PDF
Facets Of Fragmentation by Mark Murphy
PDF
Programming methodology lecture11
PPTX
Pixel Perfect
PDF
Meetup 2013:01: Uwe Kubosh - Ruboto - JRuby on Android
PDF
Ruboto at GDG Oslo 2013
PPTX
ACM Mid-Southeast Slides
PPTX
Learn Creative Coding: Begin Programming with the Processing Language
PPTX
Learn Creative Coding: Begin Programming with the Processing Language
PDF
Metal Programming Guide Tutorial And Reference Via Swift Clayton
PPTX
P5js syracuse dev meetup 20181218
KEY
Mo devtablet johncromartie-graphicsperformance
PPTX
UI and UX for Mobile Developers
PDF
The Unusual Suspect: Layouts for sleeker KDE applications
#PDR15 - Developing for Round
pebble - Building apps on pebble
Pebble NYC Meetup Pebble Time Round and the Chalk SDK
Pebble Watch iOS SDK Overview
Some examples of shapes are Lines, Polygons, Axes, and Text
Processing presentation
Announcing Pebble SDK 2.0
Facets Of Fragmentation by Mark Murphy
Programming methodology lecture11
Pixel Perfect
Meetup 2013:01: Uwe Kubosh - Ruboto - JRuby on Android
Ruboto at GDG Oslo 2013
ACM Mid-Southeast Slides
Learn Creative Coding: Begin Programming with the Processing Language
Learn Creative Coding: Begin Programming with the Processing Language
Metal Programming Guide Tutorial And Reference Via Swift Clayton
P5js syracuse dev meetup 20181218
Mo devtablet johncromartie-graphicsperformance
UI and UX for Mobile Developers
The Unusual Suspect: Layouts for sleeker KDE applications
Ad

More from Pebble Technology (17)

PDF
#PDR15 - Awesome Appstore Assets
PDF
#PDR15 - Smartstrap Workshop
PDF
#PDR15 - Data Analytics and Pebble
PDF
#PDR15 - Best Use Cases For Timeline
PDF
#PDR15 - PebbleKit iOS 3.0
PDF
#PDR15 - Voice API
PDF
#PDR15 - Designing for Pebble
PDF
#PDR15 Kick-Off
PDF
Pebble Slate Workshop
PDF
Overlay & Libraries | Pebble Meetup Oct. 2014
PDF
Connecting Pebble to the World
PDF
Guest Presentation - Strap | Pebble Developer Retreat 2014
PDF
Battery Life | Pebble Developer Retreat 2014
PDF
Thomas Sarlandie Kickoff Talk | Pebble Developer Retreat 2014
PDF
Advanced Techniques: Size | Pebble Developer Retreat 2014
PDF
Advanced Techniques: Graphics | Pebble Developer Retreat 2014
PDF
Pebble wearables devcon
#PDR15 - Awesome Appstore Assets
#PDR15 - Smartstrap Workshop
#PDR15 - Data Analytics and Pebble
#PDR15 - Best Use Cases For Timeline
#PDR15 - PebbleKit iOS 3.0
#PDR15 - Voice API
#PDR15 - Designing for Pebble
#PDR15 Kick-Off
Pebble Slate Workshop
Overlay & Libraries | Pebble Meetup Oct. 2014
Connecting Pebble to the World
Guest Presentation - Strap | Pebble Developer Retreat 2014
Battery Life | Pebble Developer Retreat 2014
Thomas Sarlandie Kickoff Talk | Pebble Developer Retreat 2014
Advanced Techniques: Size | Pebble Developer Retreat 2014
Advanced Techniques: Graphics | Pebble Developer Retreat 2014
Pebble wearables devcon

Recently uploaded (20)

PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
NewMind AI Monthly Chronicles - July 2025
PPTX
Cloud computing and distributed systems.
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
Modernizing your data center with Dell and AMD
PPT
Teaching material agriculture food technology
PDF
cuic standard and advanced reporting.pdf
PDF
Electronic commerce courselecture one. Pdf
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPTX
Big Data Technologies - Introduction.pptx
PPTX
A Presentation on Artificial Intelligence
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Encapsulation_ Review paper, used for researhc scholars
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Reach Out and Touch Someone: Haptics and Empathic Computing
Shreyas Phanse Resume: Experienced Backend Engineer | Java • Spring Boot • Ka...
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
NewMind AI Monthly Chronicles - July 2025
Cloud computing and distributed systems.
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
The Rise and Fall of 3GPP – Time for a Sabbatical?
“AI and Expert System Decision Support & Business Intelligence Systems”
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Modernizing your data center with Dell and AMD
Teaching material agriculture food technology
cuic standard and advanced reporting.pdf
Electronic commerce courselecture one. Pdf
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Big Data Technologies - Introduction.pptx
A Presentation on Artificial Intelligence
NewMind AI Weekly Chronicles - August'25 Week I
Encapsulation_ Review paper, used for researhc scholars

#PDR15 Creating Pebble Apps for Aplite, Basalt, and Chalk

  • 1. 2015 Pebble Developer Retreat Creating Pebble Apps for Aplite, Basalt, and Chalk Kevin Conley, Firmware Engineer
  • 2. Why Target Multiple Platforms?
  • 4. Continuity 1. User installs your app on an older platform 2. User upgrades to a new Pebble 3. User already has the most recent version of your app in their locker :)
  • 7. Agenda • Runtime Platforms vs. SDK Versions • Colors • Display Shapes • SDK Compatibility
  • 9. “Runtime Platform” • Set of runtime attributes and capabilities • Does NOT refer to a single watch hardware model
  • 10. Aplite 2.x ✅ Pebble Classic Pebble Steel OS Compatibility • 2.x is legacy • 3.x for Aplite coming soon!
  • 11. Aplite Basalt 2.x ✅ 3.0 ❌ ✅ Pebble Time Pebble Time Steel OS Compatibility
  • 12. Aplite Basalt Chalk 2.x ✅ 3.0 ❌ ✅ 3.6 ❌ ✅ Pebble Time Round OS Compatibility
  • 13. Aplite Basalt Chalk CPU 24k
 ARM Cortex M3 64k ARM Cortex M4 Resolution 144x168px
 (Rectangular) 180x180px (Circular) Color Black & White 64 Colors SmartStrap ❌ ✅ Mic ❌ ✅ Hardware Features
  • 14. Aplite, Basalt, and Chalk are runtime platforms, not SDK versions.
  • 15. Aplite Basalt Chalk 2.x ✅ 3.0 ❌ ✅ 3.6 ❌ ✅ 3.x ✅ Unified 3.x SDK later this year OS Compatibility
  • 16. runs on 2.x 3.x Aplite Basalt 2.x Aplite ✅ ✅ 3.x Basalt ❌ ✅ compiledagainst 2.x Aplite runs on 3.x Basalt SDK Compatibility
  • 17. runs on 2.x 3.x Aplite Aplite Basalt Chalk 2.x Aplite ✅ ✅ ✅ ❌ 3.x Aplite ❌ ✅ ❌ ❌ Basalt ❌ ❌ ✅ ❌ Chalk ❌ ❌ ❌ ✅ compiledagainst 3.x apps are compiled per platform SDK Compatibility
  • 19. Colors • Aplite •Black and white • Basalt and Chalk •64 colors •Pebble color picker tool: • https://developer.getpebble.com/more/color-picker •Download color palettes for Photoshop, GIMP, ImageMagick • https://developer.getpebble.com/guides/pebble-apps/display- and-animations/intro-to-colors/#color-palettes
  • 20. Open-Source Dithering Library • Created by Mathew Reiss • Display approximate colors beyond provided color palette • https://github.com/mathewreiss/dithering Aplite: shades of gray Basalt/Chalk: > 300 colors
  • 24. Organize Code Using Color Macros #define PBL_IF_COLOR_ELSE(if_true, if_false) #define PBL_IF_BW_ELSE(if_true, if_false) Returns if_true on watches that support color and if_false otherwise Returns if_true on watches that only support black & white and if_false otherwise
  • 25. Organize Code Using Color Defines #define PBL_COLOR #define PBL_BW Defined on watches that support color and undefined otherwise Defined on watches that only support black & white and undefined otherwise
  • 26. Color-Specific Resources "resources": {
 "media": [
 {
 "type": "png",
 "name": "MY_IMAGE",
 "file": "images/image.png"
 }
 ]
 }
 appinfo.json: resources/ images/ image~bw.png image~color.png image~bw.png image~color.png
  • 27. WatchInfoColor GColor text_foreground_color, text_background_color; switch(watch_info_get_color()) {
 case WATCH_INFO_COLOR_TIME_STEEL_GOLD:
 // White on red theme text_foreground_color = GColorWhite; text_background_color = GColorRed;
 break;
 case WATCH_INFO_COLOR_TIME_ROUND_BLACK_20:
 // White on black theme
 text_foreground_color = GColorWhite; text_background_color = GColorBlack;
 break;
 /* Other cases... */ 
 } text_layer_set_text_color(s_label_layer, text_foreground_color);
 text_layer_set_background_color(s_label_layer, text_background_color); • Customize your app’s UI using the color of the Pebble watch
  • 29. Display Shapes • Round has 7% more visible pixels than rectangular Rectangular (144 x 168) Round (visible) Round (total) (180 x 180) (0, 0)
  • 30. Calculate Position/Size as a Function of Other Layers const Layer *window_layer = window_get_root_layer(window); const GRect text_frame = GRect(0, 72, 144, 20); const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));
  • 31. Calculate Position/Size as a Function of Other Layers const Layer *window_layer = window_get_root_layer(window); const GRect text_frame = GRect(0, 72, 144, 20); const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));
  • 32. Calculate Position/Size as a Function of Other Layers const Layer *window_layer = window_get_root_layer(window); const GRect text_frame = GRect(0, 72, 144, 20); const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));
  • 33. Calculate Position/Size as a Function of Other Layers const Layer *window_layer = window_get_root_layer(window); const GRect window_bounds = layer_get_bounds(window_layer); const int16_t text_height = 20; const int16_t text_frame_y = (window_bounds.size.h - text_height) / 2; const GRect text_frame = (GRect) { .origin = GPoint(0, text_frame_y), .size = GSize(window_bounds.size.w, text_height) }; const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));
  • 34. void grect_align(GRect *rect, const GRect *inside_rect, const GAlign alignment, const bool clip); GAlignCenter clip = false rect inside_rect Align Rectangles Within Other Rectangles Using grect_align() inside_rect GAlignTop clip = true rect
  • 35. typedef enum GAlign {
 GAlignCenter,
 GAlignTopLeft,
 GAlignTopRight,
 GAlignTop,
 GAlignLeft,
 GAlignBottom,
 GAlignRight,
 GAlignBottomRight,
 GAlignBottomLeft
 } GAlign; GAlignBottomRight GAlignRight GAlignTopRight GAlignBottom GAlignCenter GAlignTop GAlignBottomLeft GAlignLeft GAlignTopLeft
  • 36. const GRect a = grect_inset(rect, GEdgeInsets(16)); // all sides const GRect b = grect_inset(rect, GEdgeInsets(25, 16)); // top & bottom, right & left const GRect c = grect_inset(rect, GEdgeInsets(25, 16, 10)); // top, right & left, bottom const GRect d = grect_inset(rect, GEdgeInsets(25, -20, 10, 16)); // top, right, bottom, left a b c d 16px 16px 25px 25px 25px 25px 10px 10px 16px 16px 16px 16px16px 16px 16px 20px Inset Rectangles Using grect_inset() and GEdgeInsets()
  • 37. rect GPoint grect_center_point(const GRect *rect); result Find the Center of Rectangles Using grect_center_point()
  • 38. • Use PBL_IF_[ROUND | RECT]_ELSE() macros to choose values or simple expressions based on the display shape • Similar to PBL_IF_[COLOR | BW]_ELSE() Organize Code Using Display Shape Macros/Defines const uint16_t area = PBL_IF_ROUND_ELSE(PI * (width * width / 4), (width * height));
  • 39. • For multiple lines, choose code using PBL_[ROUND | RECT] defines #if defined(PBL_ROUND)
 const GPoint center_point = grect_center_point(&rect);
 graphics_fill_circle(ctx, center_point, radius);
 #else const uint16_t corner_radius = 0;
 graphics_fill_rect(ctx, rect, corner_radius, GCornerNone);
 #endif Organize Code Using Display Shape Macros/Defines
  • 40. • For > 5 lines, break code into functions with _rect/_round suffixes • If the functions share the same arguments, you can simplify calling them by using the PBL_IF_[ROUND | RECT]_ELSE() macro • Otherwise, use the PBL_[ROUND | RECT] defines: Organize Code Using Display Shape Macros/Defines PBL_IF_RECT_ELSE(function_rect, function_round)(arg1, arg2, ...); #if defined(PBL_RECT)
 function_rect(arg1);
 #else
 function_round(arg1, arg2);
 #endif
  • 41. • For > 2 display-specific functions, break into separate _rect/_round files • Use wrapper file to choose appropriate implementation using display shape defines/macros • Alternatively, if the files share a common API: • (also requires ifdef’ing out implementations) Organize Code Using Display Shape Macros/Defines // main.c #if defined(PBL_RECT)
 #include “file_rect.h"
 #else
 #include “file_round.h"
 #endif src/ file_rect.c file_rect.h file_round.c file_round.h file.c file.h main.c
  • 42. Display-Specific Resources "resources": {
 "media": [
 {
 "type": "png",
 "name": "MY_IMAGE",
 "file": "images/image.png"
 }
 ]
 }
 resources/ images/ image~rect.png image~color~round.png appinfo.json: image~rect.png image~color~round.png • Most-specific resource will be used • Ambiguity will result in compiler error
  • 43. Two-Pixel Margin on Round Displays • Extend background colors to all outer edges • Avoid thin rings around the edge of the display • Manufacturing variations may result in off-center appearance • Instead, use thick rings or significantly inset from the edge
  • 44. Platform-Specific Designs “Caltrain” by Katharine Berry: https://github.com/Katharine/pebble-caltrain/ Rectangular Round
  • 46. Status Bar • For SDK 2.x, the status bar appears by default and insets the window from the top unless you use: window_set_fullscreen(window, true); • For SDK 3.x, the status bar is a UI component you must create, configure, and add to the layer hierarchy yourself: static StatusBarLayer *s_status_bar;
 s_status_bar = status_bar_layer_create();
 status_bar_layer_set_separator_mode(s_status_bar, StatusBarLayerSeparatorModeDotted);
 status_bar_layer_set_colors(s_status_bar, GColorBlack, GColorWhite);
 const Layer *status_bar_layer = status_bar_layer_get_layer(s_status_bar);
 layer_add_child(window_root_layer, status_bar_layer);
  • 49. s_status_bar = status_bar_layer_create();
 GRect status_bar_rect = window_bounds;
 status_bar_rect.size.h = STATUS_BAR_LAYER_HEIGHT;
 layer_set_frame( status_bar_layer_get_layer(s_status_bar),
 status_bar_rect);
 
 // top/bottom: statusbar, left/right: 0 const GEdgeInsets insets = GEdgeInsets( STATUS_BAR_LAYER_HEIGHT, 0); GRect menu_layer_rect = grect_inset( window_bounds, insets); Status Bar
  • 52. s_action_bar = action_bar_layer_create();
 action_bar_layer_add_to_window( s_action_bar, window);
 
 // top: statusbar, right: actionbar, bottom/left:0 const GEdgeInsets insets = GEdgeInsets(
 STATUS_BAR_LAYER_HEIGHT, ACTION_BAR_WIDTH, 0, 0);
 GRect content_rect = grect_inset( window_bounds, insets); ActionBar
  • 55. // default on Chalk menu_layer_set_center_focused(menu_layer, true); static int16_t get_cell_height_callback(MenuLayer *menu_layer, MenuIndex *cell_index,
 void *callback_context) {
 #if defined(PBL_ROUND) // when center_focused, MenuLayer can handle cell heights // that change based on the selection status if (menu_layer_is_index_selected(menu_layer, cell_index)) {
 return MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT;
 } else {
 return MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT;
 } #else return MENU_CELL_BASIC_CELL_HEIGHT; #endif
 }
 MenuLayer
  • 56. TextLayer • To maximize content visible on round displays, enable text flow and paging with an optional inset (for padding): text_layer_enable_screen_text_flow_and_paging(s_text_layer, 8);
  • 57. TextLayer • Text flow on a round display without pagination looks weird…
  • 58. WatchInfo • Inspect what watch the app is running on • Useful e.g. if you want to align visual elements with buttons • Next to different screen coordinates for different watches GRect layer_frame = GRectZero;
 switch(watch_info_get_model()) {
 case WATCH_INFO_MODEL_PEBBLE_STEEL:
 layer_frame = GRect(0, 64, 144, 44);
 break;
 case WATCH_INFO_MODEL_PEBBLE_TIME:
 layer_frame = GRect(0, 58, 144, 56);
 break;
 /* Other cases */
 }
  • 60. Platform Defines • Feature/capability defines, like PBL_COLOR, PBL_ROUND, etc. should be used in favor of these • Exception: CPU/memory-specific code #define PBL_PLATFORM_APLITE #define PBL_PLATFORM_BASALT #define PBL_PLATFORM_CHALK
  • 61. Platform Specific Resources • Can also use ~aplite, ~basalt, and ~chalk suffixes • But overall, in most cases it’s best to use ~rect, ~round, ~bw, ~color suffixes instead "resources": {
 "media": [
 {
 "type": "png",
 "name": "MY_IMAGE",
 "file": “images/image.png”, "targetPlatforms": [ "basalt" ]
 }
 ]
 }

  • 62. GPoint shim_gpoint_from_polar(GRect rect, int32_t angle) {
 #if defined(PBL_SDK_2) const GPoint center = GPoint(rect.origin.x + (rect.size.w / 2), rect.origin.y + (rect.size.h / 2)); const int16_t radius = MIN(rect.size.w, rect.size.h) / 2;
 return GPoint((sin_lookup(angle) * radius / TRIG_MAX_RATIO) + center.x,
 (-cos_lookup(angle) * radius / TRIG_MAX_RATIO) + center.y);
 #else
 return gpoint_from_polar(rect, GOvalScaleModeFitCircle, angle);
 #endif
 } Shims • Newer SDK 3.x functions do not exist in SDK 2.x • You can create shims for them using PBL_SDK_2/3 defines:
  • 63. PebbleKit JS API Availability • Some PebbleKit JS APIs don’t exist on Pebble Classic mobile apps, • Check if they are available and fail gracefully if necessary if (Pebble.getActiveWatchInfo) { // API is available; use it! var info = Pebble.getActiveWatchInfo(); console.log('Pebble model: ' + info.model); } else { // Gracefully handle unavailable API }
  • 64. Check Return Values • APIs may return NULL if: • Unsupported by platform • Permissions not given • Phone disconnected • Check the return value before using it and fail gracefully if necessary const DictationSession *session = dictation_session_create(0, callback, callback_context); if (session) { /* Success! */ dictation_session_start(session); } else { /* DictationSession is unsupported, need to fail gracefully here! */ }
  • 65. Marcel: Friday 1 pm Starting with Chalk, you need to compile with PebbleKit iOS 3.0 Since PebbleOS 3.0, you need to compile with PebbleKit Android 3.0 AndroidiOS Mobile App Compatibility
  • 66. Running Multiple Emulators at Once • pebble install --emulator=aplite && pebble install --emulator=basalt && pebble install —-emulator=chalk
  • 67. Automating Taking Screenshots • pebble screenshot --emulator=aplite && pebble screenshot --emulator=basalt && pebble screenshot —-emulator=chalk
  • 68. Recap • Runtime Platforms vs. SDK Versions • Colors • Display Shapes • SDK Compatibility