Damian Mehers' Blog Android, VR and Wearables from Geneva, Switzerland.

20Jan/161

Radical surgery: Slimming Pebble apps down to run on Aplite

A long way to go

In December 2015, when first I released Powernoter, an unofficial Evernote client for the Pebble Watch, I initially targeted Pebble Time (codename Basalt), and Pebble Time Round (codename Chalk).

After all there was already the official Evernote Pebble app (which I also created) for the original Pebble (codename Aplite).

Then Pebble released a firmware update and SDK for the original Pebble which meant that I could easily release Powernoter for the original Pebble too, using the same SDK I'd already used.

This is the build log from the first time I built Powernoter targeting Aplite (the original Pebble), Basalt (Pebble Time) and Chalk (Pebble Time Round):

-------------------------------------------------------
BASALT APP MEMORY USAGE
Total size of resources:        26461 bytes / 256KB
Total footprint in RAM:         25895 bytes / 64KB
Free RAM available (heap):      39641 bytes
------------------------------------------------------- 
...
-------------------------------------------------------
CHALK APP MEMORY USAGE
Total size of resources:        26461 bytes / 256KB
Total footprint in RAM:         25943 bytes / 64KB
Free RAM available (heap):      39593 bytes
------------------------------------------------------- 
...
-------------------------------------------------------
APLITE APP MEMORY USAGE
Total size of resources:        26341 bytes / 125KB
Total footprint in RAM:         23789 bytes / 24KB
Free RAM available (heap):      787 bytes
------------------------------------------------------- 

See the 787 bytes on the last line? That was how much free memory my app had before it even started running on an original Pebble. Before it created its first window or allocated memory to receive and send messages.

Although I successfully built Powernoter for Aplite, it couldn't even start up, crashing immediately as it ran out of memory.

Not so verbose with the error messages

The first thing I did, was to run the pebble analyze-size command, which gave me a sense of where the memory was being used.

Like all good programmers, I very carefully and very consistently checked all OS calls for out of memory situations, and logged (very) verbose messages if I ran out of memory. Like this:

  bitmap_layer = bitmap_layer_create(image_layer_size);
  if(!bitmap_layer) {
    APP_LOG(APP_LOG_LEVEL_ERROR, "Couldn't allocate memory for the image");
    ...

All those strings had to be allocated somewhere. I went through my app and removed all those lovely descriptive messages. Instead I just logged the line number - that was enough to work out where it went wrong.

  bitmap_layer = bitmap_layer_create(image_layer_size);
  if(!bitmap_layer) {
    OOMCF();
    ...

I defined a couple of macros for Out Of Memory (OOM) situations:

#define OOM(s) log_oom(__FILE_NAME__, __LINE__, (int)s)
#define OOMCF() log_create_failed(__FILE_NAME__, __LINE__)
void log_create_failed(char* file, int line) {
  app_log(APP_LOG_LEVEL_DEBUG, file, line, "create failed %d free", (int)heap_bytes_free());
}

void log_oom(char* file, int line, int size) {
  app_log(APP_LOG_LEVEL_DEBUG, file, line, "oom %d, %d", size, (int)heap_bytes_free());
}

I also declared some handy logging macros, so that debug log strings were stripped out of shipping builds

#ifdef SHIPPING
#define LOG_MEM_START()
#define LOG_MEM_END()
#define LOG_FUNC_START(name)
#define LOG_FUNC_END(name)
#define LOG_DBG(fmt, args...)
#define LOG_ERR(fmt, args...) app_log(APP_LOG_LEVEL_ERROR, __FILE_NAME__, __LINE__, " ")
#else
#define LOG_DBG(fmt, args...) app_log(APP_LOG_LEVEL_DEBUG, __FILE_NAME__, __LINE__, fmt, ## args)
#define LOG_MEM_START() app_log(APP_LOG_LEVEL_DEBUG, __FILE_NAME__, __LINE__, "start %d", (int)heap_bytes_free())
#define LOG_MEM_END() app_log(APP_LOG_LEVEL_DEBUG, __FILE_NAME__, __LINE__, "end %d", (int)heap_bytes_free())
#define LOG_FUNC_START(name) app_log(APP_LOG_LEVEL_DEBUG, __FILE_NAME__, __LINE__, "%s invoked", name)
#define LOG_FUNC_END(name) app_log(APP_LOG_LEVEL_DEBUG, __FILE_NAME__, __LINE__, "%s returning", name)
#define LOG_ERR(fmt, args...) app_log(APP_LOG_LEVEL_ERROR, __FILE_NAME__, __LINE__, fmt, ## args)
#endif

Use statics in moderation

Next I looked into how I was defining static variables. I like statics because they are only visible to the file in which they are declared: a primitive form of encapsulation. A typical C source file might have started with:

static CustomMenu* customMenu;
static CustomMenuItem* items;
static uint16_t itemCount;
static AppTimer *send_timeout_timer;
static NoteSelectedCallback noteSelectedCallback;

The types don't matter (CustomMenu is my own class that does things like automatically scrolling long menu items).

What matters is that I have four pointers and a short declared as statics, meaning I have a whole chunk of memory statically allocated just for this one file.

Powernoter is not a small app ... this multiplied by tens of files means that I had a load of memory statically allocated, which was never used unless the user was actually invoking the functionality represented by those files.

The solution was to move to a dynamically allocated memory:

typedef struct NoteList {
  CustomMenu *customMenu;
  CustomMenuItem *items;
  uint16_t itemCount;
  AppTimer *send_timeout_timer;
  NoteSelectedCallback noteSelectedCallback;
} NoteList;

I only allocate a NoteList when it is being used, and free it as soon as possible.

Omit needless code

Although the SDK includes definitions for things like DictationSession on Aplite, so that code can be compiled regardless of the platform (you do need to check return calls though), it made no sense to include that code at all. I #ifdefed whole chunks of code to reduce the app size:

#ifdef SUPPORTS_VOICE
static void dictation_session_callback(DictationSession *session, DictationSessionStatus status,
                                       char *transcription, void *context) {
  LOG_FUNC_START("dictation_session_callback");
  if(DictationSessionStatusSuccess == status) {
    if(!noteContext->waitingAnimation) {
      if(noteContext->customMenu) {
        layer_set_hidden(custom_menu_get_layer(noteContext->customMenu), true);
      }
...
}
#endif

SUPPORTS_VOICE is my own macro:

#ifndef PBL_PLATFORM_APLITE
#define SUPPORTS_VOICE
#else
#define LOW_MEMORY_DEVICE
#endif

Pebble have added a PBL_MICROPHONE macro so my use of SUPPORTS_VOICE is no longer necessary.

I did the same thing for animations and color support.

Although I think I am a decent enough software engineer, I am under few illusions as to my abilities as a designer, which is why I let you choose your very own foreground and background colors in Powernoter, except if you are running on an original Pebble, in which case all that code, including the color names, is #ifdefed out.

Be careful what you ask for (when calling app_message_xyz_maximum)

Once upon a time were were limited to 120 or so bytes per message sent between the watch and the phone. I wrote inordinately complex code to page menu items in dynamically from the phone to the watch so that you could scroll through infinitely long menus. Then Pebble gave us what we wanted, with massive (8Kish) message buffers.

When you only have a little memory free to start with, the last thing you want to do is go allocating 8K buffers. It won't work.

My code to determine the size of the input buffer looks like this now:

#ifdef LOW_MEMORY_DEVICE
#define MAX_INBOX_SIZE 512
#else
#define MAX_INBOX_SIZE 4096
#endif

The LOW_MEMORY_DEVICE macro is set on Aplite only. Users on the original Pebble won't see an enormous number of notes listed, or a lot of a note's content, but at least they'll see something.

Make long strings into Resources

There is an excellent Internationalization sample for the Pebble. Although Powernoter isn't internationalized, there are no strings hardcoded in code ... all strings are accessed via a single point. I include the strings in a single source file in the app, except for certain very long strings, such as the About page. These I load as resources from files:

static char* loadResource(uint32_t resourceId) {
  ResHandle handle = resource_get_handle(resourceId);
  size_t res_size = resource_size(handle);

  // Copy to buffer
  char* result = (char*)malloc(res_size + 1);
  if(!result) {
    OOM(res_size);
    result = (char*)malloc(1);
    if(result) {
      *result = '\0';
    }
    return result;
  }
  resource_load(handle, (uint8_t*)result, res_size);
  result[res_size] = '\0';
  return result;
}

Once I'm done with them, I free them as quickly as possible.

Summary

In case you were wondering, this is how things look right now:

CHALK APP MEMORY USAGE
Total size of resources:        27313 bytes / 256KB
Total footprint in RAM:         24244 bytes / 64KB
Free RAM available (heap):      41292 bytes
-------------------------------------------------------
BASALT APP MEMORY USAGE
Total size of resources:        27313 bytes / 256KB
Total footprint in RAM:         24176 bytes / 64KB
Free RAM available (heap):      41360 bytes
-------------------------------------------------------
APLITE APP MEMORY USAGE
Total size of resources:        13966 bytes / 125KB
Total footprint in RAM:         17353 bytes / 24KB
Free RAM available (heap):      7223 bytes
------------------------------------------------------- 

Getting from 787 bytes free to 7,223 bytes free, so that Powernoter can really run on Aplite involved many changes, some which I'd say were generally good practice (reducing statics and instead using structs which are allocated/freed), and some less so (removing error log messages).

In general I don't think the code looks too unreadable as a result of supporting Aplite ... certainly I'd prefer not to have as many #ifdefs sprinkled throughout my code as I have, but it's not that bad.

You may also wish to check out this Pebble presentation on Pebble app memory usage.

One thing is for sure, the changes I had to make to Powernoter to get it to run on Aplite are nothing compared with the miracles the Pebble team pulled to get the original Pebble to support the same SDK as Pebble Time and Pebble Time Round.

About me

I'm an independent consultant and speaker, available for ad-hoc Pebble, Android and Android Wear and Tizen consulting and development.

If you like and use Powernoter, please consider supporting it.

On the other hand if something is missing or doesn't work, check out this Trello board where you can comment to request enhancements or report bugs.

Filed under: Pebble, Wearables 1 Comment
29May/150

Updating the Pebble Emulator python code

I recently wanted to make some changes to the Pebble emulator, which uses the PyV8 Python-JavaScript bridge to emulate the phone environment running your phone-bases JavaScript companion app.Screenshot 2015-05-29 12.46.07

These are some notes on how I did this, mainly so that I remember if I need to do it again, and also just in case it helps anyone else.

The first thing I did was to clone the Pebble Python PebbleKit JS implementation, used in the emulator. The original is at https://github.com/pebble/pypkjs and mine is at https://github.com/DamianMehers/pypkjs

Once I'd done that I cloned my one locally onto my Mac, and followed the instructions to build it.

It needs a copy of the Pebble qemu open source emulator to which to talk, and I started off trying to clone the Pebble qemu and build it locally.  Half-way through it occurred to me that I already had a perfectly good qemu locally, since I already had the Pebble Dev Kit installed.

By running a pbw in the emulator, with the debug switch enabled, I was able to determine the magic command to start the emulator locally:

Screenshot 2015-05-29 12.51.32

I copied the command, added some quotes around parameters that needed them, and was able to launch the emulator in one window:

Screenshot 2015-05-29 12.51.32

The phone simulator in another window:

Screenshot 2015-05-29 12.54.04

And then my app in another:

Screenshot 2015-05-29 12.55.08

Once I was up and running I started making changes to the Python code.  Since I've never written a line of Python before I made liberal use of existing code to make the changes I needed.

It all ended well when my pull request containing my changes to support sending binary data was accepted into the official Pebble codebase, meaning that Evernote now runs in the emulator.

Filed under: Pebble No Comments
22Jan/151

Scrolling long Pebble menu items

This is a technical blog post.  Warning: contains code.

We recently pushed version 1.2 of Evernote for the Pebble to the Pebble App Store.  It is a minor release, with one bug fix, and one new feature.

The bug fix is related to support for the additional character sets that Pebble can now display.

The enhancement is what this blog post is about.  Since we released the first version of the app, which was generally well received, we’ve received emails from people complaining that their note titles, notebook names, tag names etc. don’t fit on the Pebble screen.  They are cut off, and hard to read.  People asked if we could make menu items scroll horizontally if they didn’t fit.

My response was generally something along the lines of “sorry, but we use the Pebble’s built-in menuing system, and until they support scrolling menu items horizontally, we can’t do anything”.  I never felt great about this response, but it was the genuine situation.  However before I pushed the 1.2 release with the character-set bug-fix, I thought I’d take a look at scrolling the menu items.  Turns out, it was surprisingly easy.

You can see what I’m talking about here:

 

The funny thing about the Evernote Pebble watch app is that it knows almost nothing about Evernote.  The Evernote intelligence is all delegated to the companion app that runs on the Phone.  The watch app knows how to display massive menus (paging items in and out as necessary), checkboxes, images, text etc. 

When the user scrolls to a new menu item, we kick off a wait timer using app_timer_register waiting for one second.  If the user scrolls to another menu item before the timer has expired, we wait for a new second, this time using app_timer_reschedule:

static void selection_changed_callback(Layer *cell_layer, MenuIndex new_index, MenuIndex old_index, 
void *data) {
WindowData* window_data = (WindowData*)data;
window_data->moving_forwards_in_menu = new_index.row >= old_index.row;
if(!window_data->menu_reloading_to_scroll) {
initiate_menu_scroll_timer(window_data);
} else {
window_data->menu_reloading_to_scroll = false;
}
}

The above method is called by the Pebble framework when the user scrolls to a new menu item.  The check for menu_reloading_to_scroll is called to work around some behavior I’ve seen.  This callback invokes the following method:

static void initiate_menu_scroll_timer(WindowData* window_data) {
// If there is already a timer then reschedule it, otherwise create one
bool need_to_create_timer = true;
window_data->scrolling_still_required = true;
window_data->menu_scroll_offset = 0;
window_data->menu_reloading_to_scroll = false;
if(window_data->menu_scroll_timer) {
// APP_LOG(APP_LOG_LEVEL_DEBUG, "Rescheduling timer");
need_to_create_timer = !app_timer_reschedule(window_data->menu_scroll_timer,
SCROLL_MENU_ITEM_WAIT_TIMER);
}
if(need_to_create_timer) {
// APP_LOG(APP_LOG_LEVEL_DEBUG, "Creating timer");
window_data->menu_scroll_timer = app_timer_register(SCROLL_MENU_ITEM_WAIT_TIMER,
scroll_menu_callback, window_data);
}
}

As you can see it uses a WindowsData structure, which is a custom structure associated with the current window via window_set_user_data.  Once the timer expires it calls scroll_menu_callback:

static void scroll_menu_callback(void* data) {
WindowData* window_data = (WindowData*)data;
if(!window_data->menu) {
return;
}
window_data->menu_scroll_timer = NULL;
window_data->menu_scroll_offset++;
if(!window_data->scrolling_still_required) {
return;
}

// Redraw the menu with this scroll offset
MenuIndex menuIndex = menu_layer_get_selected_index(window_data->menu);
if(menuIndex.row != 0) {
window_data->menu_reloading_to_scroll = true;
}
window_data->scrolling_still_required = false;
menu_layer_reload_data(window_data->menu);
window_data->menu_scroll_timer = app_timer_register(SCROLL_MENU_ITEM_TIMER, scroll_menu_callback,
window_data);
}

This code is called once when the timer initiated by initiate_scroll_menu_timer expires (after the one second delay), and then it invokes itself repeatedly using a shorter delay (a fifth of a second), until the menu item is fully scrolled.  The call to menu_layer_reload_data is what causes the menu to be redrawn, using the menu_scroll_offset to indicate how much to scroll the text by.

This is the method that gets called by the draw_row_callback to get the text to be displayed for each menu item:

void get_menu_text(WindowData* window_data, int index, char** text, char** subtext) {
MenuItem* menu_item = getMenuItem(window_data, index);
*text = menu_item ? menu_item->text : NULL;
*subtext = menu_item && menu_item->flags & ITEM_FLAG_TWO_LINER ?
menu_item->text + strlen(menu_item->text) + 1 : NULL;
if(*subtext != NULL && strlen(*subtext) == 0) {
*subtext = NULL;
}

MenuIndex menuIndex = menu_layer_get_selected_index(window_data->menu);
if(*text && menuIndex.row == index) {
int len = strlen(*text);
if(len - MENU_CHARS_VISIBLE - window_data->menu_scroll_offset > 0) {
*text += window_data->menu_scroll_offset;
window_data->scrolling_still_required = true;
}
}
}

The bolded code “scrolls” the text if the row corresponds to the currently selected item by indexing into the text to be displayed, and indicating that scrolling is still required.  I’m not happy with using the fixed size MENU_CHARS_VISIBLE to decide whether or not to scroll – it would be much nicer to measure the text and see if it fits.  If you know of a simple way to do this please comment!

The final thing I needed to do was to actually send longer menu item text from the phone to the watch.  Since Pebble now support sending more than 120 or so bytes this was much easier.  I’m sending up to 32 characters now.

In summary I’m simply using a timer to redisplay the menu, each time scrolling the current menu item’s text by indexing into the character array, and I stop the timer once it has all been displayed.

Filed under: Pebble, Wearables 1 Comment
1May/142

Evernote on your Pebble: your desktop duplicated?

At first glance it might look as though Evernote on the Pebble is a simply a clone of Evernote for the desktop.  image  pebble-screenshot_2014-03-15_13-17-53pebble-screenshot_2014-03-15_15-01-35

That would make absolutely no sense whatsoever, given that the Pebble has an entirely different form factor, with very different uses.

I’d like to share some of the ways in which Evernote on the Pebble has been tailored to the wrist-based experience, and what you can do to get the most out of it.   But first …

A step back … why wearables?

Earlier this year at the MCE conference I presented a hierarchy of uses for wearable devices:

  • Notifications, especially smart notifications based on your context, for example based on your current location, or who you are with, such as those provided by Google Now;
  • Sensors, especially health sensors, but also environmental sensors. Very soon we will examine the devices of someone who just died, as a kind of black box to determine what happened.
  • Control of the environment around you, such as the music playing on your phone or your house lights. The key is that you have to be able to do it without thinking about it … maybe gesture-based controls.
  • Capture of information, such as taking audio notes, or photos from your watch or Glass.
  • Consumption of information, such as viewing Evernote notes.  The key to this being useful is that the effort to view the information on your watch must be significantly lower than the effort to pull out your phone, unlock it, start the appropriate app, and navigate/search for the information.  Ideally the information should be pre-prepared for easy consumption based on your context, such as where you are, or what you are doing.

How does Evernote fit in?

Notifications work without the Evernote Pebble app

The Pebble already provides notifications from apps, so that when an Evernote reminder notification fires on your Phone …

05  … you’ll see that notification on your watch… 07

As the Evernote phone apps become more sophisticated about providing smarter, context-based notifications, you’ll get that for free on your watch. 

The Evernote app for the Pebble is very much focused on the last item in that list: consumption.

Easy access to your most important information: Your Shortcuts

On the desktop and mobile versions of Evernote, you use Shortcuts to give you easy, instant access to your most important information. Perhaps its information that you always need to have at your fingertips, or that you are working on right now.

08

It stands to reason that on the Pebble we’d give you an easy way to access those Shortcuts, and we do:

09 10

But wouldn’t it be cool if you could access your most important information, your shortcuts, as soon as you start Evernote? 

]1112

We thought so too, which is why you can put your Shortcuts at the top level menu, before all the other Evernote menu items, so that you can see your most important stuff instantly:

131415

Context-sensitive information: nearby notes

If you are about to walk into a meeting, or into a restaurant, then nearby notes are your friend:

16

This shows the notes that you created closest to your current location (yes, you can toggle between miles and kilometers), so that if you are about to go into a meeting with someone …

17

… you can quickly remind yourself about the person you are about to meet:

1819

Activity-sensitive information: a custom checklist experience

Ideally Evernote for the Pebble would automatically detect that you are in the supermarket, and present you with your shopping list.  It doesn’t do that yet, but it does make it easy for you to check and uncheck checkboxes.

Specifically it looks for all your notes that have unchecked checkboxes in them, and presents them as a list.  If you choose one, then it just displays the checkboxes from the notes, and lets you check/uncheck them.

This makes for a super-convenient shopping experience.  If you’ve ever had to juggle a small child in one hand, a supermarket trolley in the other hand, and a mobile phone in the other hand, you’ll really appreciate being able to quickly and easily check items off, as you buy them:

pebble-screenshot_2014-03-15_15-02-202122

What’s more, if you remembered to use Evernote on your phone take a photo of the yoghurt pot back home, because you knew that you were likely to be overwhelmed when faced with a vast array of dairy produce at the shop …

24

… then you can navigate to that note on your watch, and glance at the photo:

25

The Pebble’s screen is quite small, and black-and-white, so you may need to squint a little to make out the photo!

Easy access to your most important notes: Reminders

If you don’t make much use of Reminders, then you might be a little puzzled to see a dedicated Reminders menu item on the Pebble:

26

The reason is that many many people use Reminders as a way of “pinning” important notes to the top of their notes list.  Reminders are always shown at the top of the note list on the desktop apps:

27

On your Pebble you have quick and easy access to these important notes:

26pebble-screenshot_2014-03-15_21-57-31

You can view a reminder:

29

And you can mark it as “done” by long-pressing:

30

Information at a glance.  When is it a chore, and when is it a glance?

The ideal Evernote experience on your watch gives you instant access to your most important information.  Evernote on the Pebble does this by giving you quick and easy access to your shortcuts, nearby notes, checklists and reminders.

But sometimes, that isn’t enough.  Then you have a choice: do you pull out your phone, unlock it, start Evernote, and search or navigate to the information you want? Or, if it is a small text note, might it be easier to navigate to it on your watch?

Depending on what kind of a person you are, and on how you use Evernote, the idea of navigating to your notes on your watch, by drilling down using Tags (for example) might seem either laughably complex, or super-cool and powerful.  If you are an early-adopter of wearable technology, for example if you were a Pebble Kickstarter backer, then chances are you fall into the second camp.

This is the reason for the other menu items I have not discussed above: Notebooks, Tags, and Saved Searches.  For some people, it would be much easier to quickly drill down to a note on their watch, than to pull out their phone.

31343536

Glancability may not be a real word, but if it were, it would be in the eye of the beholder.

The future of Evernote on wearables

By providing you with a customized experience on the Pebble, Evernote serves you information based on what is most important to you (shortcuts and reminders), what makes sense based on your current context (nearby notes, checklist notes) as well as the more traditional ways of accessing your notes (notebooks, tags, saved searches).

These are very early days for wearable technologies.  Evernote for the Pebble is a start … as the capabilities of wearable devices evolve, so will your Evernote wearable experience.  Evernote is very much about working in symbiosis with you, completing your thoughts for you, providing information to you before you even know you need it.  There is so much more to come.

Filed under: Evernote, Pebble 2 Comments
10Nov/130

Evernote, JavaScript and the Pebble watch

Pebble just released a public beta of their new SDK,  version 2.0, and one of the more intriguing features is the ability to write JavaScript code that executes within the Pebble phone app.

No, that’s not a typo, the JavaScript executes on the phone.

Why would you possibly want to write JavaScript on the your phone?  For very good reasons:  Pebble watch apps can generally do very little by themselves.  Instead they communicate with a custom app that runs on the phone, and that custom phone app does stuff on behalf of the watch app.

For example if I wanted to write an Evernote client than ran on the Pebble watch, I’d code the watch app in C, and then create a custom Android app in Java.  My watch app could talk to my Android app, which could in turn talk to Evernote.  This is in fact what I did in an experimental client I talked about recently at a conference.

Android and iOS

But the Pebble doesn’t just support Android: it also supports the iPhone.  If I wanted to extend my app to iOS I’d need to write an equivalent app in Objective C, or C#. I’d have to duplicate my coding and testing.  There are also Bluetooth issues on iOS with the Pebble, which stop more than one app from communicating with the watch at a time, and the app needs to be whitelisted.

So when Pebble announced that they supported writing JavaScript apps that ran on the phone, hosted within a JavaScript engine that ran within the Pebble phone app on both Android and iOS, this seemed appealing.  I’d not need to write a custom Android app, and a custom iOS app.  Instead I could code up the phone functionality in JavaScript, and all would be good.

But first, I need to check that I could talk to Evernote’s cloud API using the Pebble JavaScript implementation ...

Evernote and Thrift

The Evernote service API is exposed using a technology called Thrift.  Originally developed at Facebook, and now hosted by the Apache Foundation, you define your API using a Thrift Interface Definition Language (IDL).  This IDL is consumed by Thrift code generators which generate language-specific bindings allowing you to access the interface from Java, C#, PHP, and many other languages, including, of course, JavaScript.  The generated code talks to a Thrift runtime, which sends and receives bits over the wire to the corresponding service.

I thought I’d start off by making the Pebble phone-based JavaScript app talk to Evernote, and then once I had that working, I could make it talk in turn to my C watch app.

I downloaded the Evernote JavaScript SDK from GitHub which contains both the generated “proxy” classes generated from the Thrift IDL, and the Thrift Runtime classes, both in JavaScript.

I decided to start simple, and just list my Evernote notebooks:

// Get these by creating an account and logging in to sandbox.evernote.com and then
// going to https://sandbox.evernote.com/api/DeveloperToken.action
var authTokenEvernote = "...";
var noteStoreURL = "https://sandbox.evernote.com/shard/s1/notestore";

// We want to talk the Thrift Binary protocol over HTTP. These classes are in the
// Thrift runtime
var noteStoreTransport = new Thrift.BinaryHttpTransport(noteStoreURL);
var noteStoreProtocol = new Thrift.BinaryProtocol(noteStoreTransport);

// We want to talk to Evernote's NoteStore service. This is generated code
var noteStore = new NoteStoreClient(noteStoreProtocol);

// Ask Evernote what notebooks I have
noteStore.listNotebooks(authTokenEvernote, function (notebooks) {
console.log(notebooks);
});

I put this into a file I called main.js but I also needed to include the Thrift runtime (thrift.js and pebble-js-app.js) and the generated Evernote proxies (including NoteStore_types.js and NoteStore.js).

Since this release of the Pebble SDK only supports a single JavaScript file, named pebble-js-app.js, I borrowed a script volunteered by Matthew Tole that validated the JavaScript and merged all the files together in a single file, and built and ran the Pebble app:

clear
jshint js/main.js || { exit 1; }
jshint pebble/appinfo.json || { exit 1; }

#uglifyjs js/libs/evernote-sdk-js/evernote-sdk-js/thrift/lib/thrift.js ... js/main.js -o pebble/src/js/pebble-js-app.js
cat js/libs/evernote-sdk-js/evernote-sdk-js/thrift/lib/thrift.js ... js/main.js > pebble/src/js/pebble-js-app.js
cd pebble
pebble clean
pebble build || { exit 1; }
if [ "$1" = "install" ]; then
pebble install --logs
fi
 

The first error I hit when I tried running the code, is that the Thrift JavaScript runtime expects to be able to use the DataView class, which is part of the JavaScript Typed Arrays mechanism, a work in progress:

[INFO    ] * :0 JS: pbnote: JavaScript app ready and running!
[INFO ] * :0 Error: pbnote: ReferenceError: Can't find variable: DataView at line 819 in pebble-js-app.js

It turns out although it is not documented, or supported, that there is a partial Typed Array implementation: the Pebble JavaScript Engine has an implementation of the ArrayBuffer type, as well as Int8Array, Uint8Array, etc.  There are some peculiarities, such as the ArrayBuffer supporting the slice method, but not the byteLength property.

So I went looking for an ArrayBuffer implementation I could use, and came across Joshua Bell’s implementation on GitHub.

I included it in the build script, but it did nothing.  Upon further examination, I discovered that it looks to see if there is an existing implementation and (very politely) does nothing if it finds one.  I didn’t want that:

global.ArrayBuffer = ArrayBuffer;
// global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer;

I ran once again, but this time I hit a brick wall.  This is the code flow for the call to noteStore.ListNotebooks above:

  • My code calls listNotebooks which is in the NoteStore.js generated class
  • listNotebooks calls send_listNotebooks in NoteStore.js
  • send_listNotebooks writes out a listNotebooks message to the BinaryProtocol Thrift Runtime type I initialized in my code above
  • listNotebooks continues with a call to send in the BinaryHttpTransport Thrift Runtime type I also initialized above
  • the BinaryHttpTransport initializes an XMLHttpRequest and sends off the message that was built up previously.

The trick though, is how it sends the message, which you can see in the last line of this code:

var xhr = new XMLHttpRequest();
xhr.open('POST', this.url, /*async*/true);
xhr.setRequestHeader('Content-Type', 'application/x-thrift');
xhr.setRequestHeader('Accept', 'application/x-thrift');
xhr.responseType = 'arraybuffer';

xhr.onload = function (evt) {
this.received = xhr.response;
this.offset = 0;
try {
var value = recv_method.call(client);
} catch (exception) {
//console.log(JSON.stringify(exception));
value = exception;
callback = onerror;
}
callback(value);
}.bind(this);

xhr.onerror = function (evt) {
//console.log(JSON.stringify(evt));
onerror(evt);
};

xhr.send(postData.buffer);

It attempts to send a Uint8Array’s buffer.  I hacked up a dummy web server, and it turns out I was receiving the result of a toString call, which was something like “object ArrayBuffer”…

The Pebble JavaScript engine’s XMLHttpRequest only supports sending string data, not binary data.  It doesn’t support sending Typed Arrays.

I tried all kinds of things, but finally admitted defeat, for now.

The concept of having your watch app talk to JavaScript code, which in turn talks to the outside world is very appealing.  It means that you don’t have to write, test and maintain separate iOS and Android “companion” apps for your watch app.

I am sure that for most web based services what Pebble provides will be more than enough.  Unfortunately for binary based interfaces, such as the Evernote interface, the current XMLHttpRequest support isn’t quite rich enough.

Yet.

Filed under: Evernote, Pebble No Comments
28Oct/130

Constraints foster creativity: Pebble watch app development

This is a recording of the presentation I gave at the Softshake Conference in Geneva in October 2013.

In this presentation I live-code a Pebble app from scratch and send it messages from a corresponding Android App. I share my experience and insights creating an experimental Evernote client for the Pebble watch. How do you deal with writing code in C, with no malloc available?

What do you do when the maximum message size between your phone and your watch is 120 bytes? What does it mean to create a useful app for your watch?

Filed under: Evernote, Pebble No Comments
5Jul/130

My Pebble development setup

I’ve been spending a little time developing an app for the Pebble watch recently, and I thought I’d share my development environment setup, so as to give other people some ideas.  I’m interested in hearing how you develop for the Pebble too.

This is my development setup:

2013-07-05 22.07.49

You’ll notice the Macbook Air sitting behind the keyboard.  Although it appears to be unused, everything is actually running on that.  There I have the Pebble SDK set up, and I’m using Parallels to a host Windows virtual machine.

I’m using Microsoft’s Remote Desktop to remote into that Windows VM from a desktop machine with four monitors.  Remote Desktop supports multiple monitors, which means that although my Mac only has the single built-in (unused) monitor, the Windows VM now has four monitors attached to it.

image

I use the monitor on the top right-hand side to run VNC in the VM to give me a window back into my Mac environment, so that I can compile my Pebble watch code using the Pebble SDK.

Yes, I’m using Remote Desktop to remote into a VM running on the Mac, and then using VNC to go from that VM back into the Mac!

image

image

The monitor on the right is rotated to portrait, and runs Sublime Text, to edit the Mac based Pebble-specific C code, opened from the Mac via the network.

image

image

The monitor on the left is used for Web Browsing, to read the Pebble reference documentation, etc.  I also use it to trigger installs of my watch app using Chrome-to-Phone:

image

image

The center monitor is used to run IntelliJ to edit the Android code that my watch app talks to. 

I also use it to run Microsoft’s Visual Studio.  I have a C Visual Studio project and I’ve included references to files in my Pebble C app code from the Mac.  By carefully organizing my code I’m able to put non Pebble-specific code into separate C source files, which I can then include and test within Visual Studio – stuff like buffer management code, which is tricky to get right, and which you really don’t want to be testing on the watch where there is no debugger.

image

Of course it doesn’t have to be like this – it probably seems kind of complicated, but it works well for me.  How do you develop and test your Pebble code?

Filed under: Pebble No Comments
31Mar/133

Evernote integration with Pebble

Pebble

I signed up for the Pebble Kickstarter for a couple of reasons.

Firstly I actually wanted the watch – a waterproof watch that I could use to receive notifications without having to get the phone out of my pocket, and which I could use to control podcasts, audiobooks and music.

Secondly I wanted to be able to program it.  The full SDK is not yet available, so I can’t write programs that run on the watch, but it is possible to write Android apps that communicate with the watch.

I’ve spent some time today writing a small app that integrates the Pebble device with Evernote.

Evernote

One of the great things about Evernote is that it is very simple and easy to use at one level, but as your usage becomes more sophisticated, and your requirements deepen, you’ll find that it is much more powerful and capable than it first appears.

An example of this is that notes you create can optionally contain geolocation information, and you can view your notes on a map to see which ones you created on a particular vacation or business trip.

The idea

Vacation Location

I thought I’d use this Evernote Note geolocation capability to create an app that reminds you when you get close to the location associated with certain notes.

ColosseumAtEvening

Imagine going on vacation and creating a series of notes in Evernote for particular locations that you want to visit.  Whenever you get close to one of those locations your Pebble buzzes and you are reminded to visit it.

Evernote Food graphicRestaurant Reminder

Or imagine using Evernote Food to remember restaurants you want to visit, and your watch buzzing with a reminder if you happen to be close to one of those restaurants when it is meal time

I decided to implement it by having my app look for notes that have geolocation information (lat/lon) and are tagged with a particular tag (“pebble”).

When the app starts, it looks for those notes and uses the Android proximity service to set up proximity alerts for the note locations.  Then when the app receives the proximity alert it sends the note title as notification to the watch and could launch that note in Evernote.

image

The app

 

photo

This code uses the raw Evernote API, however there is an excellent Evernote Android SDK available, and if you are building an Android app that uses Evernote you should definitely check it out.

package com.everpebble;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.*;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import com.evernote.edam.notestore.*;
import com.evernote.edam.type.*;
import com.evernote.edam.userstore.UserStore;
import com.evernote.thrift.protocol.TBinaryProtocol;
import com.evernote.thrift.transport.THttpClient;
import org.json.*;

import java.util.HashMap;
import java.util.Map;

public class Main extends Activity {

  private static final String evernoteHost = "sandbox.evernote.com";
  private static final String userStoreUrl = "https://" + evernoteHost + "/edam/user";
  private static final String TAG = "EverPebble";

  private static final String PROX_ALERT_INTENT = "com.everpebble.ProximityAlert";

  // Available from https://sandbox.evernote.com/api/DeveloperToken.action
  private static final String authToken = "TBS";

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    // Register for proximity events off the UI thread
    new Thread(new Runnable() {
      @Override
      public void run() {
        registerEvernoteProximityNotes();
      }
    }).start();
  }

  /**
   * Finds all notes tagged with a particular tag that have geolocation information, and registers for
   * Android proximity alerts when the phone gets close to those note locations
   */
  private void registerEvernoteProximityNotes() {
    THttpClient userStoreTrans;
    try {
      // Connect to Evernote to discover the note store URL
      userStoreTrans = new THttpClient(userStoreUrl);
      TBinaryProtocol userStoreProt = new TBinaryProtocol(userStoreTrans);
      UserStore.Client userStore = new UserStore.Client(userStoreProt, userStoreProt);
      String notestoreUrl = userStore.getNoteStoreUrl(authToken);

      // Set up the NoteStore client
      THttpClient noteStoreTrans = new THttpClient(notestoreUrl);
      TBinaryProtocol noteStoreProt = new TBinaryProtocol(noteStoreTrans);
      NoteStore.Client noteStore = new NoteStore.Client(noteStoreProt, noteStoreProt);

      // Find notes Tagged with "pebble" that have geolocation information
      NoteFilter noteFilter = new NoteFilter();
      noteFilter.setWords("tag:pebble latitude:* longitude:*");
      NoteList result = noteStore.findNotes(authToken, noteFilter, 0, 50);
      Log.i(TAG, "Found: " + result.getTotalNotes());

      // Register for proximity alerts
      LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
      for(Note note : result.getNotes()) {
        NoteAttributes attributes = note.getAttributes();
        if(attributes != null) {
          Intent intent = new Intent(PROX_ALERT_INTENT);
          
          // Save the note title and guid in the intent so that when we are close
          // we can send the title to the Pebble
          intent.putExtra("Title", note.getTitle());
          intent.putExtra("Guid", note.getGuid());
          PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
          
          // We want to be notified when we are within 100 meters of the location
          double lat =attributes.getLatitude();
          double lon = attributes.getLongitude();
          locationManager.addProximityAlert(lat, lon,  100, -1, proximityIntent);
        }
      }

      // Ensure we receive the event intents
      IntentFilter filter = new IntentFilter(PROX_ALERT_INTENT);
      registerReceiver(new ProximityIntentReceiver(), filter);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Sends alerts to the pebble phone when entering within range of a note's geolocation
   */
  public class ProximityIntentReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
      Log.d(TAG, "Received proximity alert");
      boolean entering = intent.getBooleanExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);

      final String title = intent.getStringExtra("Title");

      if(entering && title != null) {
        new Thread(new Runnable() {
          @Override
          public void run() {
            sendAlertToPebble("Evernote", title + " is nearby");
          }
        }).start();
      }
    }
  }


  public void sendAlertToPebble(String title, String body) {
    Log.d(TAG, "sendAlertToPebble invoked");
    final Intent i = new Intent("com.getpebble.action.SEND_NOTIFICATION");

    final Map<String, Object> data = new HashMap<String, Object>();
    data.put("title", title);
    data.put("body", body);
    final JSONObject jsonData = new JSONObject(data);
    final String notificationData = new JSONArray().put(jsonData).toString();

    i.putExtra("messageType", "PEBBLE_ALERT");
    i.putExtra("sender", "EverPebble");
    i.putExtra("notificationData", notificationData);

    Log.d(TAG, "About to send a modal alert to Pebble: " + notificationData);
    sendBroadcast(i);
  }
}

Summary

It is very early days for the Pebble, but its been great fun using it, and even more fun programming to it by integrating it with Evernote

Disclaimer: I do work for Evernote, but this blog is entirely my personal private work.