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

Top three mobile application threats

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.

Combat fraud and increase customer satisfaction

More from The Register

next story
Feast your PUNY eyes on highest resolution phone display EVER
Too much pixel dust for your strained eyeballs to handle
Samsung Galaxy S5 fingerprint scanner hacked in just 4 DAYS
Sammy's newbie cooked slower than iPhone, also costs more to build
Microsoft lobs pre-release Windows Phone 8.1 at devs who dare
App makers can load it before anyone else, but if they do they're stuck with it
Report: Apple seeking to raise iPhone 6 price by a HUNDRED BUCKS
'Well, that 5c experiment didn't go so well – let's try the other direction'
Rounded corners? Pah! Amazon's '3D phone has eye-tracking tech'
Now THAT'S what we call a proper new feature
Zucker punched: Google gobbles Facebook-wooed Titan Aerospace
Up, up and away in my beautiful balloon flying broadband-bot
Leaked pics show EMBIGGENED iPhone 6 screen
Fat-fingered fanbois rejoice over Chinternet snaps
US mobile firms cave on kill switch, agree to install anti-theft code
Slow and kludgy rollout will protect corporate profits
AMD unveils Godzilla's graphics card – 'the world's fastest, period'
The Radeon R9 295X2: Water-cooled, 5,632 stream processors, 11.5TFLOPS
Sony battery recall as VAIO goes out with a bang, not a whimper
The perils of having Panasonic as a partner
prev story

Whitepapers

Designing a defence for mobile apps
In this whitepaper learn the various considerations for defending mobile applications; from the mobile application architecture itself to the myriad testing technologies needed to properly assess mobile applications risk.
3 Big data security analytics techniques
Applying these Big Data security analytics techniques can help you make your business safer by detecting attacks early, before significant damage is done.
Five 3D headsets to be won!
We were so impressed by the Durovis Dive headset we’ve asked the company to give some away to Reg readers.
The benefits of software based PBX
Why you should break free from your proprietary PBX and how to leverage your existing server hardware.
Securing web applications made simple and scalable
In this whitepaper learn how automated security testing can provide a simple and scalable way to protect your web applications.