Feeds

It's all in the wrist: How to write apps for the Pebble smartwatch

Code and example video - the (bouncing on-screen) ball's in your court

Internet Security Threat Report 2014

Resources boom

The timers and buttons can be used to start and stop and count lap times in a stopwatch app, for instance. Or you might create simple games like Snake or Breakout. Two utilities, httpPebble on iOS and Pebble Connect on Android allow Pebble apps to route web requests through your phone, allowing you to create a variety of check-at-a-glance apps for the smartwatch. There’s a scope in the Pebble SDK and OS to create some interesting and useful apps.

As it stands, the app appears in the Pebble’s menu simply as a name, Ball, which is entered into the boilerplate PBL_APP_INFO created by the SDK’s create_pebble_project.py script. This also sets the app’s unique UUID, which you’ll see at the top of the file. You can also modify this to set the app’s version number and to add your name as author. But what’s really needed is a menu icon, and you can add one by editing the resource_map.json created for you in the /resources/src folder within the project folder.

The app in action

Menu icons need to be monochrome 28 x 28 .png files with no transparency, and it’s easy enough to make on in any graphics package. Actually, transparency is supported, but you need to tweak the image’s ‘type’ in its entry in the resource map, which is read by the compiler and used to add necessary resources into the app binary, from ‘png’ to ‘png-trans’. It’s easier to flatten the image and stick with ‘png’. Other pre-defined resource types are ‘font’ and ‘raw’, the latter a catch-all for every other data type you want to use. Adding the image to the app’s resource map is just a matter of adding some keywords into the media section of the boilerplate copy:

{
    "friendlyVersion": "VERSION",
    "versionDefName": "APP_RESOURCES",
    "media": 
    [
        {
           "defName": "IMAGE_MENU_ICON",
           "type": "png",
           "file": "icon.png"
        }
   ]
}

This assigns the image the resource name (defName) IMAGE_MENU_ICON. This label is pre-inserted into the app code in the PBL_APP_INFO, prefixed with RESOURCE_ID_, which the SDK adds to each resource’s defName to create its resource ID. You can give the image whatever defName you like, as long as you prefix it with RESOURCE_ID_ when you edit the PBL_APP_INFO code:

PBL_APP_INFO(MY_UUID,
             "Ball", 
             "Tony Smith",
             1, 
             0,
             RESOURCE_ID_IMAGE_MENU_ICON,
             APP_INFO_STANDARD_APP);

The APP_INFO_STANDARD_APP flag tells the Pebble it’s dealing with an app rather than a watch face, which is a special kind of app managed by the Pebble’s on-board watch application. For the latter, replace APP_INFO_STANDARD_APP with APP_INFO_WATCH_FACE.

Compile and transfer the app, and you’ll see your icon in the menu.

The app’s icon image is handled automatically by the OS. Other resources need to be initialised before they’re used, and this needs to take place within the handle_init() function you’ve written. You do this by adding the line

        resource_init_current_app(&APP_RESOURCES);

before any resources are used. The &APP_RESOURCE input comes from the .json file’s versionDefName field. Whatever versionDefName you enter into the .json file - the SDK simply drops in VERSION - make sure you use it in the resource_init_current_app() function call too.

Resources can then be loaded into RAM using the resource_load() function, which takes the resource’s handle (ResHandle) as an argument, along with a buffer in which to store the bits and the size of the resource in bytes. That handle is generated with the resource_get_handle() function to which you pass the resource’s resource ID - which is not the defName, remember, though it’s easy to convert from one to the other by adding RESOURCE_ID_ at the start.

If it’s a bitmap you’re loading, say to present icons that represent what the Pebble’s three main buttons do, the SDK provides the convenience function bmp_init_container(), which takes a resource ID and a pointer to a Bitmap Container (BmpContainer) structure into which the resource data will be stored. The BmpContainer can then be added to a new Layer which, in turn, can be added to the root Layer as one of its child Layers.

Graphics resources loaded this way can be used to replace the demo app’s simple circle with a picture of a ball, perhaps with some additional images to make it appear to be squashed against the edges of the screen just before it bounces. I dropped in vibes_short_pulse() calls so the Pebble momentarily vibrates every time the ball bounces. I’ve made some other tweaks to the code listed above too - they’re all in the complete listing below. ®

test_project.c

#include "pebble_os.h"
#include "pebble_app.h"
#include "pebble_fonts.h"


#define MY_UUID { 0x64, 0x8C, 0xE8, 0xC6, 0xBF, 0x52, 0x46, 0x5F, 0x95, 0x0E, 0x8F, 0xA2, 0xD8, 0x70, 0x5A, 0xB1 }

PBL_APP_INFO(MY_UUID,
             "Ball", "Black Pyramid Software",
             1, 0, /* App version */
             RESOURCE_ID_IMAGE_MENU_ICON,
             APP_INFO_STANDARD_APP);

#define time_duration 100


// Function Prototypes

void draw_layer(Layer *layer, GContext *gctxt);
void config_provider(ClickConfig **config, Window *winder);
void up_single_click_handler(ClickRecognizerRef recognizer, Window *winder);
void down_single_click_handler(ClickRecognizerRef recognizer, Window *winder);
void shift_single_click_handler(ClickRecognizerRef recognizer, Window *winder);


// Globals

Window window;
AppTimerHandle timerHandle;
int pos_x, pos_y, delta_x, delta_y, old_x, old_y;
bool initialWipeFlag;


// Special Functions

void draw_layer(Layer *layer, GContext *gctxt)
{
    if (initialWipeFlag)
    {
        // Erase screen on first run
        
        graphics_context_set_fill_color(gctxt, GColorBlack);
        GRect rect = GRect(0,0,144,168);
        graphics_fill_rect(gctxt, rect, 0, GCornerNone);
        initialWipeFlag = false;
    }
    
    // Wipe old circle
    
    GPoint point = GPoint(old_x, old_y);
    graphics_context_set_fill_color(gctxt, GColorBlack);
    graphics_fill_circle(gctxt, point, 8);
    
    // Draw new circle
    
    point = GPoint(pos_x, pos_y);
    graphics_context_set_fill_color(gctxt, GColorWhite);
    graphics_fill_circle(gctxt, point, 8);
}


// Handler functions

void handle_init(AppContextRef ctxt)
{
    initialWipeFlag = true;
    
    window_init(&window, "Ball");
    window_set_background_color(&window, GColorBlack);
    window_set_fullscreen(&window, true);
    window_stack_push(&window, false);
    
    Layer *root = window_get_root_layer(&window);
    layer_set_update_proc(root, draw_layer);
    
    layer_mark_dirty(root);
    
    // Set up button monitoring DO AFTER PUSHING WINDOW TO STACK
    
    window_set_click_config_provider(&window, (ClickConfigProvider)config_provider);
    
    srand(time(NULL));
    
    pos_x = 68 + rand() % 8;
    pos_y = 80 + rand() % 8;
    
    delta_x = 8;
    delta_y = 8;
    
    old_x = 0;
    old_y = 0;
    
    // Set up timer
    
    timerHandle = app_timer_send_event(ctxt, 500, 1);
}


void handle_deinit(AppContextRef ctxt)
{
    app_timer_cancel_event(ctxt, timerHandle);
}


// Click configuration functions

void config_provider(ClickConfig **config, Window *winder)
{
    config[BUTTON_ID_UP]->click.handler = (ClickHandler)up_single_click_handler;
    config[BUTTON_ID_DOWN]->click.handler = (ClickHandler)down_single_click_handler;
    config[BUTTON_ID_SELECT]->click.handler = (ClickHandler)shift_single_click_handler;
}


void up_single_click_handler(ClickRecognizerRef recognizer, Window *winder)
{
    delta_x = delta_x * -1;
}


void down_single_click_handler(ClickRecognizerRef recognizer, Window *winder)
{
    delta_y = delta_y * -1;
}


void shift_single_click_handler(ClickRecognizerRef recognizer, Window *winder)
{
    old_x = pos_x;
    old_y = pos_y;
    
    pos_x = rand() % 140;
    pos_y = rand() % 160;
    
    // Tell layer to redraw
    
    Layer *root = window_get_root_layer(&window);
    layer_mark_dirty(root);
}


// Event handlers

void handle_timer(AppContextRef ctxt, AppTimerHandle handle, uint32_t cookie)
{
    old_x = pos_x;
    old_y = pos_y;
    
    pos_x = pos_x + delta_x;
    pos_y = pos_y + delta_y;
    
    if (pos_x > 140)
    {
        pos_x = 132;
        delta_x = -8;
        vibes_short_pulse();
    }
    
    if (pos_x < 4)
    {
        pos_x = 12;
        delta_x = 8;
        vibes_short_pulse();
    }
    
    if (pos_y > 162)
    {
        pos_y = 154;
        delta_y = -8;
        vibes_short_pulse();
    }
    
    if (pos_y < 4)
    {
        pos_y = 12;
        delta_y = 8;
        vibes_short_pulse();
    }
    
    // Reset timer
    
    timerHandle = app_timer_send_event(ctxt, time_duration, 1);
    
    // Tell layer to redraw
    
    Layer *root = window_get_root_layer(&window);
    layer_mark_dirty(root);
}


void handle_tick(AppContextRef ctxt, PebbleTickEvent *event)
{
    pos_x = pos_x + delta_x;
    pos_y = pos_y + delta_y;
    
    if (pos_x > 140)
    {
        pos_x = 132;
        delta_x = -8;
    }
    
    if (pos_x < 4)
    {
        pos_x = 12;
        delta_x = 8;
    }
    
    if (pos_y > 162)
    {
        pos_y = 154;
        delta_y = -8;
    }
    
    if (pos_y < 4)
    {
        pos_y = 12;
        delta_y = 8;
    }
    
    Layer *root = window_get_root_layer(&window);
    layer_mark_dirty(root);
}


// Main app entry point

void pbl_main(void *params)
{
    AppContextRef ctxt = (AppContextRef)params;
    
    PebbleAppHandlers handlers =
    {
        .init_handler = &handle_init,
        .deinit_handler = &handle_deinit,
        .timer_handler = &handle_timer
    };
  
    app_event_loop(ctxt, &handlers);
}

resource_map.json

{
    "friendlyVersion": "VERSION",
    "versionDefName": "APP_RESOURCES",
    "media": 
    [
        {
           "defName": "IMAGE_MENU_ICON",
           "type": "png",
           "file": "icon.png"
        }
   ]
}

You can download the source files here, and the .pbw file can be downloaded here.

Choosing a cloud hosting partner with confidence

More from The Register

next story
Official: European members prefer to fondle Apple iPads
Only 7 of 50 parliamentarians plump for Samsung Galaxy S
Fujitsu CTO: We'll be 3D-printing tech execs in 15 years
Fleshy techie disses network neutrality, helmet-less motorcyclists
Space Commanders rebel as Elite:Dangerous kills offline mode
Frontier cops an epic kicking in its own forums ahead of December revival
Intel's LAME DUCK mobile chips gobbled by CASH COW
Chipzilla won't have money-losing mobe unit to kick about anymore
prev story

Whitepapers

Why cloud backup?
Combining the latest advancements in disk-based backup with secure, integrated, cloud technologies offer organizations fast and assured recovery of their critical enterprise data.
Forging a new future with identity relationship management
Learn about ForgeRock's next generation IRM platform and how it is designed to empower CEOS's and enterprises to engage with consumers.
How to determine if cloud backup is right for your servers
Two key factors, technical feasibility and TCO economics, that backup and IT operations managers should consider when assessing cloud backup.
Reg Reader Research: SaaS based Email and Office Productivity Tools
Read this Reg reader report which provides advice and guidance for SMBs towards the use of SaaS based email and Office productivity tools.
Getting ahead of the compliance curve
Learn about new services that make it easy to discover and manage certificates across the enterprise and how to get ahead of the compliance curve.