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

High performance access to file storage

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
Leaked pics show EMBIGGENED iPhone 6 screen
Fat-fingered fanbois rejoice over Chinternet snaps
Reg man builds smart home rig, gains SUPREME CONTROL of DOMAIN – Pics
LightwaveRF and Arduino: Bright ideas for dim DIYers
WTF happened to Pac-Man?
In his thirties and still afraid of ghosts
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
US mobile firms cave on kill switch, agree to install anti-theft code
Slow and kludgy rollout will protect corporate profits
Happy 25th birthday, Game Boy!
Monochrome handset ushered in modern mobile gaming era
Leaked photos may indicate slimmer next-generation iPad
Will iPad Air evolve into iPad Helium?
True optical zoom coming to HTC smartphone cameras
Time to ditch that heavy DSLR? Maybe in a year, year and a half
prev story

Whitepapers

Mainstay ROI - Does application security pay?
In this whitepaper learn how you and your enterprise might benefit from better software security.
Combat fraud and increase customer satisfaction
Based on their experience using HP ArcSight Enterprise Security Manager for IT security operations, Finansbank moved to HP ArcSight ESM for fraud management.
The benefits of software based PBX
Why you should break free from your proprietary PBX and how to leverage your existing server hardware.
Top three mobile application threats
Learn about three of the top mobile application security threats facing businesses today and recommendations on how to mitigate the 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.