Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Pebble Code Wizard

0.00/5 (No votes)
8 Oct 2015 1  
Creating customized Pebble projects

Introduction

Starting new projects on any platform can be a laborious process, especially if you end up doing the same boilerplate code over and over again.

Background

The command line based Pebble environment can create a couple of basic project types, which I usually then proceed to reformat into my own personal style before I start working on whatever the project actually is.
I decided to see if I could find how the Pebble projects were created and was pleasantly surprised to find it was all done by easily editable Python code.

As Comes

The Pebble command provides four standard templates:

  1. Standard an app that detects button presses and prints what was pressed to the screen (default)
  2. Worker an app for running in the background (--worker)
  3. Simple an empty project (--simple)
  4. Javascript a JavaScript based app (--javascript)

For example:

pebble new-project --simple MyProject

Will produce an empty project in a folder called MyProject.

All these project templates are Python strings with embedded replacement bits & pieces to customize the new project. These are easily editable with your favourite editor.

First Useful Changes

The files we'll be looking at are:

<Pebble SDK>/pebble-tool/pebble_tool/commands/sdk/create.py
<Pebble SDK>/pebble-tool/pebble_tool/sdk/templates.py

The first, create.py, constructs a project folder hierarchy based on the arguments given.

The second file, templates.py, holds the actual text for the files within the project.

The first useful change we can make is to find DICT_DUMMY_APPINFO and change the default company_name to our own. Something that can easily be forgotten about while working on a project.

Also in templates.py is the source of the standard app, looking for FILE_DUMMY_MAIN we can tweak the format to our preferred style and when we start a new project it's ready to go with no messing about.

NOTE: When editing Python code, the indenting is crucial, each line at a particular indent level should be using the same type of indenting, either tabs or spaces, but not a mixture.

Taking it Further

Now the basics have been uncovered we have the opportunity to do more. When I'm starting a new project, I don't really want to start with the same basic shell code, for example, I have a fledgling game "engine" I'd like to start with. Or if I'm writing a watchface, I'd like to start with a watchface shell with no button handling and a basic tick handling structure already in place.

We'll create a new project type for a watchface.

Extending the available project types in create.py, I can add a new option "--watchface" near the end of the file:

parser.add_argument("--watchface", action="store_true", help="Generate a watchface.")

And, above that, alter the code that generates the project, I have expanded the if/else which makes it easier to neatly extend later:

        # Create main .c file
        with open(os.path.join(project_src, "{}.c".format(project_name)), "w") as f:
            if args.simple:
                f.write(FILE_SIMPLE_MAIN)
            elif args.watchface:
                f.write(FILE_DUMMY_WATCHFACE)
            else:
                f.write(FILE_DUMMY_MAIN)

        # Add appinfo.json file
        appinfo_dummy = DICT_DUMMY_APPINFO.copy()
        appinfo_dummy['uuid'] = str(uuid.uuid4())
        appinfo_dummy['project_name'] = project_name
        if args.watchface:
            appinfo_dummy['is_watchface'] = 'true'
        with open(os.path.join(project_root, "appinfo.json"), "w") as f:
            f.write(FILE_DUMMY_APPINFO.substitute(**appinfo_dummy))

And finally for this file, near the top, we add a refernce to the new template string (which will be added to templates.py shortly):

from pebble_tool.sdk.templates import (FILE_SIMPLE_MAIN, FILE_DUMMY_MAIN, FILE_DUMMY_APPINFO,
                                       DICT_DUMMY_APPINFO, FILE_GITIGNORE, FILE_DUMMY_JAVASCRIPT_SRC,
                                       FILE_WSCRIPT, FILE_DUMMY_WORKER, FILE_DUMMY_WATCHFACE)

                f.write(FILE_SIMPLE_MAIN)
            elif args.watchface:
                f.write(FILE_DUMMY_WATCHFACE)
            else:
                f.write(FILE_DUMMY_MAIN)

        # Add appinfo.json file
        appinfo_dummy = DICT_DUMMY_APPINFO.copy()
        appinfo_dummy['uuid'] = str(uuid.uuid4())
        appinfo_dummy['project_name'] = project_name
        if args.watchface:
            appinfo_dummy['is_watchface'] = 'true'
        with open(os.path.join(project_root, "appinfo.json"), "w") as f:
            f.write(FILE_DUMMY_APPINFO.substitute(**appinfo_dummy))

At the bottom of templates.py, we can add the shell for our watchface:

FILE_DUMMY_WATCHFACE = """#include <pebble.h>

static Window* window = NULL;
static TextLayer* timeLayer = NULL;

static void tickHandler(struct tm* tickTime, TimeUnits unitsChanged)
{
    static char timeString[20];

    if (clock_is_24h_style())
        strftime(timeString, sizeof(timeString), "%H:%M", tickTime);
    else
        strftime(timeString, sizeof(timeString), "%I:%M", tickTime);

    text_layer_set_text(timeLayer, timeString);
}

static void windowLoad(Window* window)
{
    Layer* windowLayer = window_get_root_layer(window);
    GRect bounds = layer_get_bounds(windowLayer);

    timeLayer = text_layer_create((GRect) { .origin = { 0, 72 }, .size = { bounds.size.w, 20 } });
    text_layer_set_text(timeLayer, "Pebble");
    text_layer_set_text_alignment(timeLayer, GTextAlignmentCenter);
    layer_add_child(windowLayer, text_layer_get_layer(timeLayer));
}

static void windowUnload(Window *window)
{
    text_layer_destroy(timeLayer);
}

static void init(void)
{
    window = window_create();
    window_set_window_handlers(window, (WindowHandlers) {
        .load = windowLoad,
        .unload = windowUnload,
    });
    const bool animated = true;
    window_stack_push(window, animated);

    tick_timer_service_subscribe(MINUTE_UNIT, tickHandler);
}

static void deinit(void)
{
    tick_timer_service_unsubscribe();
    window_destroy(window);
}

int main(void)
{
    init();
        APP_LOG(APP_LOG_LEVEL_DEBUG, "Done initializing, pushed window: %p", window);
        app_event_loop();
    deinit();
}
"""

Now we can create a new project.

pebble new-project --watchface WatchFaceTest

No messing about, we now have a watchface project ready to work on. Here it is running on the Aplite emulator:

We are now in a position to add any type of started project we want, including adding various options to allow for adding extra features, for example, if we want to use the flash storage or accelerometer, etc.

The Future - SDK Updates

Pebble release new SDKs fairly regularly, but they have been following this new project method for a while now so there should be no issues merging over your personalised project templates.

Here are my personalised versions of create.py and templates.py: CustomPebbleTemplate.tar.gz

History

  • 2015-10-15: Added screen shot and extension possibilities
  • 2015-10-08: First draft

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here