Archive

Monthly Archives: February 2014

UPDATES:

– 1.2.0 shipped with an incorrect icon (d’oh!) and is now 1.2.1.

– 1.2.2 Adds link to install from Pebble Appstore to combat Android Beta 10 sideloading bug. 

Today sees the addition of a number of new features to improve and expand the Wristponder experience:

  1. Added option to send to last Caller as well as last SMS.
  2. Added auto-close option in Settings.
  3. Added visual indicator of list usage.
  4. Added a guard to size when editing.
  5. Fixed a crash when a response was too long.

Here are some screenshots:

wp screens quad

A few points regarding other requested features:

  1. Facebook Messenger, WhatsApp and Google Voice do not at the moment have APIs that allow background composition and sending of messages without user interaction. If I am incorrect, please let me know!
  2. Persistent storage of responses for faster launch on the watch is on the horizon, but it needs a few kinks ironed out first.
  3. Implementing a ‘top 5 received SMS contacts’ feature is only possible for Android phones with API level 18 (Android 4.3) or greater, so not worth implementation just yet. Still a desirable feature though. Appears to be doable! More soon.

Download

You can get Wristponder with it’s companion app in one place from the Google Play store. If you find any bugs or have feature ideas, let me know!

Get it on Google Play

Advertisements

As an experiment, I downloaded the Subway Subcard Android app and extracted the QR code from a screenshot. The screenshot has been modified to prevent anyone claiming my Subcard points (of which I currently have very few, but it’s the principle!)

subcardfull

I then created a simple Pebble app to show this code as a BitmapLayer for the store scanner to scan. It looks like this with the dummy QR code:

pebble-screenshot_2014-02-27_11-22-28

And it worked! The points were added in the Android app but I didn’t have to unlock my phone and open the Subcard app. So some time was saved, and I got a positive reaction from the cashier, which is always a bonus!

If you want to use this yourself, get the code from the GitHub repo and replace qr.png with a 144×144 crop of your own Subcard QR code from the app or carefully from a photo of your physical card, then re-compile.

In making improvements to Wristponder, I developed a mechanism that I’ve gone on to make into a small library that I’ll doubtless reuse again in the future, as can you!

example_screenshotTo use, simply copy the files into your /src folder, #include "alert.h" and call alert_show(...). You can also update an existing alert and cancel at any time.

The full details as well as an example project are on the GitHub page here. Enjoy!

Introduction

After a couple of weeks’ on and off work I proudly present a new watchapp for Pebble: Wristponder!

Together with the Android companion app, this watchapp allows you to add, edit, delete, import and export custom SMS messages and send them from the watch.

pb screen

How it works

In the Android app, the responses are specified by the user and stored in a database, which is then read when the watchapp is launched and a request is made.

Each response is sent to the watch where it is then shown in a MenuLayer underneath the name of the last contact to send the user an SMS. Once the user selects a response it is sent to that contact via the phone.

Due to AppMessage size limitations, each PebbleDictionary sent contains only two responses. If any of them fail to be delievered, the watchapp spots this and requests them again from the phone. This event is shown by ‘Latecomers…’ on the watch. This even happens less if the AppMessage delay is increased, but you can try smaller values if you like to live dangerously. This was a source of much frustration in development, but the final solution seems to be remarkably robust.

Screenshot_2014-02-14-14-50-49

Download

You can get Wristponder with it’s companion app in one place from the Google Play store. If you find any bugs or have feature ideas, let me know!

Get it on Google Play

Required Reading

Pebble SDK 2.0 Tutorial #1: Your First Watchapp

Pebble SDK 2.0 Tutorial #2: Telling the Time

Pebble SDK 2.0 Tutorial #3: Images and Fonts

Pebble SDK 2.0 Tutorial #4: Animations and Timers

Pebble SDK 2.0 Tutorial #5: Buttons and Vibrations

A basic working knowledge of JavaScript is recommended, but it shouldn’t be too hard to understand the language syntax as a beginner from the sample code provided at the end, especially coming from any Java or C related background (such as us!).

Introduction

Creating a simple Pebble watch app or watch face is well and fine, but adding an Internet connection to that app to fetch data/communicate with other services adds almost limitless potential. An example of this is my Pebble Tube Status app that fetches information on the status of the London Underground network for line info at a glance. For this tutorial section we will be getting our data from another source: The Openweathermap.org APIs, a free to use and simple example of data a watch face can display from the web.

Now, this is a long one, so make sure you have a good cup of tea or some other soothing beverage near you before you embark!

Basic Watch Face Setup

The first step this time is to create a new CloudPebble project and make sure it is set up in ‘Settings’ as a watchface, not a watch app. Next, copy in the C code below to start a bare-bones app:

#include <pebble.h>

Window* window;

void window_load(Window *window)
{

}

void window_unload(Window *window)
{

}

void init()
{
  window = window_create();
  WindowHandlers handlers = {
    .load = window_load,
    .unload = window_unload
  };
  window_set_window_handlers(window, handlers);

  window_stack_push(window, true);
}

void deinit()
{
  window_destroy(window);
}

int main(void)
{
  init();
  app_event_loop();
  deinit();
}

This is the ‘blank canvas’ on which we will build this weather info app. The next steps are to prepare the watch app to display the data we get from the weather feed. Let’s do this with four TextLayers. These will be for the ‘Openweathermap.org’ title/attribution, the location, the temperature and the time the data was fetched. As you can see from the API page linked previously, there are a lot more fields of data to display, but these will keep the tutorial simple and concise. So, here are our four global TextLayer declarations:

TextLayer *title_layer, *location_layer, *temperature_layer, *time_layer;

This time around we will take a measure to avoid the lengthy process of initialising these TextLayers by using a custom utility function to save space. As I was taught in my first year of University, functions are best used to reduce repetitive code, so this is an ideal use case. Below is a function that will set up a TextLayer to specification provided in the arguments. Place it above window_load() in the very least, as that is where it will be used:

static TextLayer* init_text_layer(GRect location, GColor colour, GColor background, const char *res_id, GTextAlignment alignment)
{
  TextLayer *layer = text_layer_create(location);
  text_layer_set_text_color(layer, colour);
  text_layer_set_background_color(layer, background);
  text_layer_set_font(layer, fonts_get_system_font(res_id));
  text_layer_set_text_alignment(layer, alignment);

  return layer;
}

Thus we can set up the title TextLayer like so in an abbreviated fashion:

void window_load(Window *window)
{
  title_layer = init_text_layer(GRect(5, 0, 144, 30), GColorBlack, GColorClear, "RESOURCE_ID_GOTHIC_18", GTextAlignmentLeft);
  text_layer_set_text(title_layer, "Openweathermap.org");
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(title_layer));
}

Take a moment to match the arguments given in the function call to its declaration and see that by using this function we can save an extra five lines per TextLayer initialisation! The rest of the other layers are set up in a similar fashion:

location_layer = init_text_layer(GRect(5, 30, 144, 30), GColorBlack, GColorClear, "RESOURCE_ID_GOTHIC_18", GTextAlignmentLeft);
text_layer_set_text(location_layer, "Location: N/A");
layer_add_child(window_get_root_layer(window), text_layer_get_layer(location_layer));

temperature_layer = init_text_layer(GRect(5, 60, 144, 30), GColorBlack, GColorClear, "RESOURCE_ID_GOTHIC_18", GTextAlignmentLeft);
text_layer_set_text(temperature_layer, "Temperature: N/A");
layer_add_child(window_get_root_layer(window), text_layer_get_layer(temperature_layer));

time_layer = init_text_layer(GRect(5, 90, 144, 30), GColorBlack, GColorClear, "RESOURCE_ID_GOTHIC_18", GTextAlignmentLeft);
text_layer_set_text(time_layer, "Last updated: N/A");
layer_add_child(window_get_root_layer(window), text_layer_get_layer(time_layer));

We mustn’t forget to destroy these in the appropriate place, as always!

void window_unload(Window *window)
{
  text_layer_destroy(title_layer);
  text_layer_destroy(location_layer);
  text_layer_destroy(temperature_layer);
  text_layer_destroy(time_layer);
}

The watch app (once compiled) should look like this:

pebble-screenshot_2014-02-02_13-16-29

Setting up AppMessage
Before we fetch the data from the Internet, we will need to set up the watch app to receive AppMessage messages from the Pebble phone app. Remember that with PebbleKit JS, the JavaScript code runs on the phone, and the results are sent via AppMessage to the watch for display. A basic overview of how that messaging system works can be seen in the “AppMessage system overview” section in the SDK 1.X Tutorial Section on the subject, but the methodology has changed with SDK 2.0. With that in mind, let’s add some basic AppMessage framework:

Step 1: Declaring keys. Keys are ‘labels’ used to tell each side of the system what the data value means. For example, a key called ‘temperature’ could have it’s associated value treated as a temperature value. The names of keys and how they are interpreted are entirely up to the programmer, as you will soon see. The list of keys we will use are shown in the declaration below:

enum {
  KEY_LOCATION = 0,
  KEY_TEMPERATURE = 1,
};

Step 2: Create a callback for receiving data from the phone. There are other callbacks for failed events, but we won’t worry about them here:

static void in_received_handler(DictionaryIterator *iter, void *context)
{

}

Step 3: Setting up AppMessage itself. This is done in init(), but before window_stack_push():

//Register AppMessage events
app_message_register_inbox_received(in_received_handler);
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());    //Largest possible input and output buffer sizes

Step 4: Set up how we will process the received Tuples. After multiple AppMessage implementations, I’ve found the most reliable method is to read the first item, then repeat reading until no more are returned, using a switch based process_tuple() function to separate out the process. Here’s how that is best done:

static void in_received_handler(DictionaryIterator *iter, void *context) 
{
	(void) context;
	
	//Get data
	Tuple *t = dict_read_first(iter);
	while(t != NULL)
	{
		process_tuple(t);
		
		//Get next
		t = dict_read_next(iter);
	}
}

Next, declare some character buffers to store the last displayed data. The function text_layer_set_text() requires that the storage for the string it displays (when not a literal) is long-lived, so let’s declare it globally:

char location_buffer[64], temperature_buffer[32], time_buffer[32];

Thus, we must define what process_tuple() will do. This is the important part, as here is where the incoming Tuples will be dissected and acted upon. The key and value of each Tuple is read and the key used in a switch statement to decide what to do with the accompanying data:

void process_tuple(Tuple *t)
{
  //Get key
  int key = t->key;

  //Get integer value, if present
  int value = t->value->int32;

  //Get string value, if present
  char string_value[32];
  strcpy(string_value, t->value->cstring);

  //Decide what to do
  switch(key) {
    case KEY_LOCATION:
      //Location received
      snprintf(location_buffer, sizeof("Location: couldbereallylongname"), "Location: %s", string_value);
      text_layer_set_text(location_layer, (char*) &location_buffer);
      break;
    case KEY_TEMPERATURE:
      //Temperature received
      snprintf(temperature_buffer, sizeof("Temperature: XX \u00B0C"), "Temperature: %d \u00B0C", value);
      text_layer_set_text(temperature_layer, (char*) &temperature_buffer);
      break;
  }

  //Set time this update came in
  time_t temp = time(NULL);
  struct tm *tm = localtime(&temp);
  strftime(time_buffer, sizeof("Last updated: XX:XX"), "Last updated: %H:%M", tm);
  text_layer_set_text(time_layer, (char*) &time_buffer);
}

That concludes the Pebble side of the system for now.

PebbleKit JS Setup
The Pebble phone app runs JavaScript code that actually fetches the data using the phone’s data connection, and then sends the results as AppMessage dictionaries to the watch for interpretation and display (as already mentioned). To start, on the left side of the CloudPebble screen, choose ‘JS’, and begin the file with this code segment to listen for when the Pebble app is opened:

Pebble.addEventListener("ready",
  function(e) {
    //App is ready to receive JS messages
  }
);

The next step is to declare the same set of keys to the JavaScript side as to the C side. To do this, go to Settings, and scroll down to ‘PebbleKit JS Message Keys’, and enter the same keys as defined in the C code , like so:


KEY_LOCATION 0
KEY_TEMPERATURE 1

Then hit ‘Save changes’.

We’ve already initialised the JavaScript file to respond when the watch app is opened, with the ‘ready’ event. Now we will modify it to request the weather information and parse the result. The code below will do that, and follows a process similar to that laid out in the Pebble weather app example. First, create a method that will connect to an URL and return the response with a XMLHttpRequest object. Here is an example method:

function HTTPGET(url) {
	var req = new XMLHttpRequest();
	req.open("GET", url, false);
	req.send(null);
	return req.responseText;
}

Next, invoke this method with the correct URL for the location you want from the Openweathermap.org API. Once this is done, we will obtain the response as plain text. It will need to be parsed as a JSON object so we can read the individual data items. After this, we construct a dictionary of the information we’re interested in using our pre-defined keys and send this to the watch. This whole process is shown below in a method called getWeather(), called in the ‘ready’ event callback:

var getWeather = function() {
	//Get weather info
	var response = HTTPGET("http://api.openweathermap.org/data/2.5/weather?q=London,uk");

	//Convert to JSON
	var json = JSON.parse(response);

	//Extract the data
	var temperature = Math.round(json.main.temp - 273.15);
	var location = json.name;

	//Console output to check all is working.
	console.log("It is " + temperature + " degrees in " + location + " today!");

	//Construct a key-value dictionary
	var dict = {"KEY_LOCATION" : location, "KEY_TEMPERATURE": temperature};

	//Send data to watch for display
	Pebble.sendAppMessage(dict);
};

Pebble.addEventListener("ready",
  function(e) {
    //App is ready to receive JS messages
	getWeather();
  }
);

After completing all this, the project is almost complete. After compiling and installing, you should get something similar to this:

pebble-screenshot_2014-02-02_18-05-17

Final Steps

So we have our web-enabled watch app working as it should. If this were a watch face, we’d want it to update itself every so often for as long as it is open. Seeing as this is a demo app, this isn’t too critical, but let’s do it anyway as a learning experience. It only requires a few more lines of C and JS.

Return to your C file and subscribe to the tick timer service for minutely updates in init(), like so:

//Register to receive minutely updates
tick_timer_service_subscribe(MINUTE_UNIT, tick_callback);

Add the corresponding de-init procedure:

tick_timer_service_unsubscribe();

And finally the add callback named in the ‘subscribe’ call (as always, above where it is registered!):

void tick_callback(struct tm *tick_time, TimeUnits units_changed)
{

}

We’re going to use this tick handler to request new updates on the weather from the phone. The next step is to create a function to use AppMessage to send something back to the phone. Below is just such a function, accepting a key and a value (be sure to add this function above the tick callback!):

void send_int(uint8_t key, uint8_t cmd)
{
	DictionaryIterator *iter;
 	app_message_outbox_begin(&iter);

 	Tuplet value = TupletInteger(key, cmd);
 	dict_write_tuplet(iter, &value);

 	app_message_outbox_send();
}

Every five minutes (it can be any interval) we will request new information. Seeing as this is the only time the watch app will ever communicate back this way, it doesn’t matter which key or value we use. It is merely a ‘hey!’ sort of message. If you wanted to distinguish between the messages sent back to the phone, you’d use the exact same method of defining keys as we did for location and temperature values. So, we change the tick handler to look a little more like this:

void tick_callback(struct tm *tick_time, TimeUnits units_changed)
{
	//Every five minutes
	if(tick_time->tm_min % 5 == 0)
	{
		//Send an arbitrary message, the response will be handled by in_received_handler()
		send_int(5, 5);
	}
}

The final piece of the puzzle is to set up the JavaScript file to respond in turn to these requests from the watch. We do that by registering to receive the ‘appmessage’ events, like so:

Pebble.addEventListener("appmessage",
  function(e) {
    //Watch wants new data!
	getWeather();
  }
);

And there we have it! Every five minutes the watch will ask for updated data, and receive this new information after the phone querys openweathermap.org.

Conclusions
That was a rather long journey, but it’s an important one for stretching the usefulness of your Pebble beyond telling the time and date! It also introduces a lot of new concepts at once, which may confuse some. If you have a query, post it here and I’ll do my best to answer it!

The full project source code that results from this Tutorial section can be found on GitHub here.

Thanks for reading, and keep an eye out for more soon!