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

Pebble SDK 2.0 Tutorial #6: AppMessage for PebbleKit JS

Pebble SDK 2.0 Tutorial #7: MenuLayers

Pebble SDK 2.0 Tutorial #8: Android App Integration

Pebble SDK 2.0 Tutorial #9: App Configuration

Introduction

In this section of the tutorial series, I will be covering some of the Event Services introduced in the 2.0 version of the Pebble SDK which have been oft requested due to their popularity in newer, interactive watchfaces. Namely:

  • Bluetooth Connection Service
  • Battery State Service
  • Accelerometer Service (tap and raw)
  • App Focus Service (not covered, but works identically to the Bluetooth Connection Service)

Setup

To begin with, we will be using the blank template from before, shown below for convenience. Create a new CloudPebble project and start a new C file with the template as its contents:

#include <pebble.h>

static Window* window;

static void window_load(Window *window)
{

}

static void window_unload(Window *window)
{

}

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

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

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

Bluetooth Connection Service
The first Event Service we will be using is the Bluetooth Connection Service, which allows us to see the current connection status as well as subscribe to updates (only while Bluetooth is actually connected, so be careful with debug logs), much in the same way as with the TickTimerService. Firstly, we will create a TextLayer in window_load() to use for showing the events happening. First is the global pointer:

static TextLayer *bt_layer;

then creation proper in window_load(). Note the use of bluetooth_connection_service_peek() to show the state of the connection at the time of creation. As always we also add the corresponding destruction function call to free memory:

static void window_load(Window *window)
{
  //Setup BT Layer
  bt_layer = text_layer_create(GRect(5, 5, 144, 30));
  text_layer_set_font(bt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
  if(bluetooth_connection_service_peek() == true)
  {
    text_layer_set_text(bt_layer, "BT: CONNECTED");
  }
  else
  {
    text_layer_set_text(bt_layer, "BT: DISCONNECTED");
  }
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(bt_layer));
}

static void window_unload(Window *window)
{
  text_layer_destroy(bt_layer);
}

Next we will subscribe to the BluetoothConnectionService to update this TextLayer whenever the status of the Bluetooth connection to the phone changes. Like a TickHandler, we start by creating a function to use as a handler with the correct signature, and fill it with logic to change the text displayed. This should be placed before init(), where it will be registered:

static void bt_handler(bool connected)
{
  if(connected == true)
  {
    text_layer_set_text(bt_layer, "BT: CONNECTED");
  }
  else
  {
    text_layer_set_text(bt_layer, "BT: DISCONNECTED");
  }
}

The final step is to perform the actual subscription, which is very easy to do, and happens in init():

//Subscribe to BluetoothConnectionService
bluetooth_connection_service_subscribe(bt_handler);

After compiling and installing the project, try disconnecting and re-connecting your phone’s Bluetooth radio a few times and observe the result.

Battery State Service
The next Event Service we will be adding will be the Battery State Service, which provides information on the Pebble’s battery. It provides more detail than a simple bool, including charging status and whether the cable is plugged in or not. As before, we will create a new TextLayer to show the output. Add the pointer to the last one in the declaration:

static TextLayer *bt_layer, *batt_layer;

Then, perform the proper creation in window_load(). This time, the information provided by the Battery State Service comes in the form on the BatteryChargeState data structure, with fields as shown in the documentation. It is worth noting that the Service only returns the battery charge in increments of 10. The setup of the new TextLayer is shown below:

//Setup Battery Layer
batt_layer = text_layer_create(GRect(5, 25, 144, 30));
text_layer_set_font(batt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(batt_layer));

//Get info, copy to long-lived buffer and display
BatteryChargeState state = battery_state_service_peek();
static char buffer[] = "Battery: 100/100";
snprintf(buffer, sizeof("Battery: 100/100"), "Battery: %d/100", state.charge_percent);
text_layer_set_text(batt_layer, buffer);

After re-compiling, the battery charge percentage should be shown below the Bluetooth status.

Accelerometer Service (tap)
The Accelerometer Service operates in a very similar manner to the previous two Event Services, but can operate in two modes: tap and raw data. The tap mode will call a handler that we subscribe when the Pebble is tapped (or wrist is shaken), whereas the raw data mode will supply X, Y and Z values at an rate we select. I’ll show both of these for the sake of completeness. An application of the latter mode can be seen here.

First, we create a further TextLayer to show the output data:

static TextLayer *bt_layer, *batt_layer, *accel_layer;

The first mode we will use is the tap mode. Let’s create the TextLayer proper in window_load():

//Setup Accel Layer
accel_layer = text_layer_create(GRect(5, 45, 144, 30));
text_layer_set_font(accel_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
text_layer_set_text(accel_layer, "Accel tap: N/A");
layer_add_child(window_get_root_layer(window), text_layer_get_layer(accel_layer));

Next, we will create the handler function to be called whenever a tap is detected, and furnish it with logic to show what kind of tap was detected:

static void accel_tap_handler(AccelAxisType axis, int32_t direction)
{
  switch(axis)
  {
  case ACCEL_AXIS_X:
    if(direction > 0)
    {
      text_layer_set_text(accel_layer, "Accel tap: X (+)");
    }
    else
    {
      text_layer_set_text(accel_layer, "Accel tap: X (-)");
    }
    break;
  case ACCEL_AXIS_Y:
    if(direction > 0)
    {
      text_layer_set_text(accel_layer, "Accel tap: Y (+)");
    }
    else
    {
      text_layer_set_text(accel_layer, "Accel tap: Y (-)");
    }
    break;
  case ACCEL_AXIS_Z:
    if(direction > 0)
    {
      text_layer_set_text(accel_layer, "Accel tap: Z (+)");
    }
    else
    {
      text_layer_set_text(accel_layer, "Accel tap: Z (-)");
    }
    break;
  }
}

Finally, we subscribe our handler function to the Accelerometer Event Service in init():

//Subscribe to AccelerometerService
accel_tap_service_subscribe(accel_tap_handler);

You should now be able to see the result of tapping the watch. Personally I’ve found that shaking the wrist is a more reliable way of triggering events (such as showing more information on a watchface), but taps can still be used as an option.

Accelerometer Service (raw data)
Finally, we will use the raw data mode of the Accelerometer Service. To do this, we will first remove the existing Accelerometer Service subscription (but still keep the handler for reference).

In the raw data mode, the data values arrive at a specific interval chosen with a call to accel_service_set_sampling_rate(), and the number of samples in a batch can be chosen using accel_service_set_samples_per_update(). We will stick with the default rate and update size for simplicity. Be aware that this mode will drain the battery significantly faster than the tap mode.

Next, we will create a new handler function to let us access the data that arrives from the Event Service. Accessing the data is as simple as reading the fields in the data parameter in the handler, as shown below:

static void accel_raw_handler(AccelData *data, uint32_t num_samples)
{
  static char buffer[] = "XYZ: 9999 / 9999 / 9999";
  snprintf(buffer, sizeof("XYZ: 9999 / 9999 / 9999"), "XYZ: %d / %d / %d", data[0].x, data[0].y, data[0].z);
  text_layer_set_text(accel_layer, buffer);
}

Finally, we add the new subscription, making sure we have disabled the one one in init():

//Subscribe to AccelerometerService (uncomment one to choose)
//accel_tap_service_subscribe(accel_tap_handler);
accel_data_service_subscribe(1, accel_raw_handler);

Now this is all in place, re-compile and re-install the watch app to see the live values. Try tilting the watch in each axis to see the constant g acceleration act on each in turn.

The final result should look like this:
final

Conclusion
So, that’s the new Event Services. As I mentioned, there is another called the App Focus Service which tells you when your app is covered by a notification, but it works in a very similar way to the Bluetooth Connection Service, so you should be able to figure it out!

The full source code can be found here on GitHub.

Later today I will be (finally!) graduating from university, and to mark the occasion I will be wearing a special watchface bearing the university’s coat of arms, which will be a cool example to show anyone who asks about my Pebble. Exciting!

image

smsrepeater

My mum recently got a new phone, a Moto G, after my recommendation. It’s a big step up from Samsung S8000 Jet she had before (one of Samsung’s last phones before adopting Android, and which had a beta version of Android 2.1 a.k.a JetDroid installed, which I had a small hand in).

One of the features she liked most about it was the option to have the SMS tone repeat every minute, which was useful if it went off when she was out of hearing range (this handset was before notification LEDs or smartwatches!). This feature was markedly missing from Android 4.4.2 KitKat which is currently on here phone, so I offered to create an app to emulate this feature, because I could and wanted to see if I could.

After two hours, a brief foray into Timers, AlarmManagers, AsyncTasks and Handlers, I arrived at a solution that uses a BroadcastReceiver to detect an incoming SMS, start a Service once every 1, 2, 5 or 10 minutes using an AlarmManager Alarm and re-emit the default notification tone. This cycle is broken when the keyguard is removed when the phone is unlocked to answer the SMS. She was very pleased to have the feature back, which makes up for the predictive keyboard I forced on her!

repeater-screeny

If anyone is interested in using this or taking it further (I’m sure there are already many apps like this!), you can find the source code here.

Updates:
15/07/14 – Added option to change story detail view font size.

31/5/14 – The news category and number of items is now exposed as a configuration page

18/06/14 – v1.2.0 streams stories instead of pre-loading them for extra speed. Configuration page now shows version numbers and update news.

Most of my Pebble apps so far have either been watchfaces or control watchapps – those that control a camera (Watch Trigger), SMS sending (Wristponder) or radios (Data Toggle). Another (and some would say the primary use of a Pebble smartwatch) is to be a data display device, rather than data input. With this in mind I decided to make a watchapp that I would myself use on a daily basis that involved data fetching and formatting for display. News is the obvious application that came to me, so after studying the BBC’s public news RSS feeds I came up with this:

bbc newsWhen the user opens the watchapp, PebbleKit JS fetches the latest data from the RSS feed and creates 15 Story objects that contain the headline and the short description of the news story. These are streamed (using ACK callbacks for maximum speed) to the watch and displayed in a MenuLayer to the user. When the user clicks SELECT on a news item, the full summary is shown. The splash screen also uses my recently developed ProgressBarLayer object to show download progress.

Using the configuration skill recently gained from finally experimenting it is possible (and I’d like to do it) to use the configuration process to allow the user to choose their news category (such as Science and Technology or Sport), but for now the main headlines seem enough.

You can get this app from the Pebble Appstore. Enjoy!

 

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

Pebble SDK 2.0 Tutorial #6: AppMessage for PebbleKit JS

Pebble SDK 2.0 Tutorial #7: MenuLayers

Pebble SDK 2.0 Tutorial #8: Android App Integration

Introduction

In this section of the tutorial series we will create a basic app that can be configured from the Pebble app. Lots of watchfaces and watchapps use this functionality to let the user tweak various aspects to their own liking. Watchfaces I’ve created before SDK 2.0 get around this by having each tweak in a separate watchface package, which lead to having five or six of the same watchface.

I’ve not yet gotten around to adding configuration to any of my watchfaces (although I plan to in the future) due to the fact that the configuration pages loaded from the Pebble app are not included in the watchapp package itself but are loaded from a remote website, and I have no web hosting to speak of. However, I have since discovered (although I’m sure I’m not the first) that such a page can be hosted on Dropbox. It must be in the Public folder, otherwise it is offered as a download and not as a webpage to view.

Let’s get started!

Watchapp Setup
The watchapp we will be creating will have a single option to keep things simple – the option to invert the colours. To begin with, create a new project and use the following code as a starting point:

#include <pebble.h>

static Window *window;
static TextLayer *text_layer;

static void window_load(Window *window) 
{
  //Create TextLayer
  text_layer = text_layer_create(GRect(0, 0, 144, 168));
  text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
  text_layer_set_text_color(text_layer, GColorBlack);
  text_layer_set_background_color(text_layer, GColorWhite);
  text_layer_set_text(text_layer, "Not inverted!");

  layer_add_child(window_get_root_layer(window), text_layer_get_layer(text_layer));
}

static void window_unload(Window *window) 
{
  text_layer_destroy(text_layer);
}

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

  window_stack_push(window, true);
}

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

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

This should be familiar: a basic app that has a single TextLayer stating that the app is not inverted. The process I’ve adopted to setup app configuration has the following steps:

1. Setup AppMessage to enable messages containing option data to be sent from PebbleKit JS.
2. Setup the app to be configurable in appinfo.json, or the Settings page in CloudPebble.
3. Setup PebbleKit JS code to load the configuration page and send the result to the watch.
4. Write the HTML page that presents an interface to the user to allow them to choose their options.

Setting Up AppMessage
We will start by declaring the key we will be using to receive the option to invert the watchapp. Don’t forget to declare this in Settings on CloudPebble or in appinfo.json if you are working with the native SDK:

#define KEY_INVERT 0

Next, we create the AppMessageInboxReceived handler that will process any received messages. If they contain our key, we will compare the payload value cstring to set the colours of the app to be inverted or not, depending on the value received. We then use the Persistent Storage API to save the result for the next time the watchapp is opened. This should be placed above init() as it will be called there in a moment:

static void in_recv_handler(DictionaryIterator *iterator, void *context)
{
  //Get Tuple
  Tuple *t = dict_read_first(iterator);
  if(t)
  {
    switch(t->key)
    {
    case KEY_INVERT:
      //It's the KEY_INVERT key
      if(strcmp(t->value->cstring, "on") == 0)
      {
        //Set and save as inverted
        text_layer_set_text_color(text_layer, GColorWhite);
        text_layer_set_background_color(text_layer, GColorBlack);
        text_layer_set_text(text_layer, "Inverted!");

        persist_write_bool(KEY_INVERT, true);
      }
      else if(strcmp(t->value->cstring, "off") == 0)
      {
        //Set and save as not inverted
        text_layer_set_text_color(text_layer, GColorBlack);
        text_layer_set_background_color(text_layer, GColorWhite);
        text_layer_set_text(text_layer, "Not inverted!");

        persist_write_bool(KEY_INVERT, false);
      }
      break;
    }
  }
}

The final step is to actually open AppMessage to enable communication with the phone. Do this in init():

app_message_register_inbox_received((AppMessageInboxReceived) in_recv_handler);
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());

Note we used the app_message_inbox_size_maximum() and app_message_outbox_size_maximum() functions to get the maximum buffer sizes available. While not strictly required here, it is a good best practice. I’ve wasted a lot of time in past projects not realising the buffer sizes I’d chosen were too small!

The final step is to set up the app to load the last stored configuration when the app is restarted, and takes for form of a similar if, else as the AppMessageInboxReceived handler above. Again, we use the Persistent Storage API to get our last saved configuration value. The window_load()function becomes thus:

static void window_load(Window *window) 
{
  //Check for saved option
  bool inverted = persist_read_bool(KEY_INVERT);

  //Create TextLayer
  text_layer = text_layer_create(GRect(0, 0, 144, 168));
  text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));

  //Option-specific setup
  if(inverted == true)
  {
    text_layer_set_text_color(text_layer, GColorWhite);
    text_layer_set_background_color(text_layer, GColorBlack);
    text_layer_set_text(text_layer, "Inverted!");
  }
  else
  {
    text_layer_set_text_color(text_layer, GColorBlack);
    text_layer_set_background_color(text_layer, GColorWhite);
    text_layer_set_text(text_layer, "Not inverted!");
  }

  layer_add_child(window_get_root_layer(window), text_layer_get_layer(text_layer));
}

Now the C code is complete!

PebbleKit JS Setup
The PebbleKit JS component of the app is the part responsible for loading the configuration page and sends the results of the user interaction to the watch to be processed as we just set up. This is done through the “showConfiguration” and “webviewclosed” events. Here is our initial JS code. Add a new JS file in CloudPebble or to the src/js/pebble-js-app.js if coding natively:

Pebble.addEventListener("ready",
  function(e) {
    console.log("PebbleKit JS ready!");
  }
);

So far, so simple. Next, we add an event listener for the “showConfiguration” event, triggered when a user chooses the Settings button in the Pebble app, like that shown below:

Screenshot_2014-05-24-15-04-23

The job of this event listener is to call Pebble.openURL(), a requirement of the system. This is when the configuration page is loaded (we will design this later). As stated in the introduction a good place to store this file is in your Public Dropbox folder. This way it is shown as a webpage and not as a download. Use mine for the moment, but if you want to make any changes you will need to change this to point to your own file:

Pebble.addEventListener("showConfiguration",
  function(e) {
    //Load the remote config page
    Pebble.openURL("https://dl.dropboxusercontent.com/u/10824180/pebble%20config%20pages/sdktut9-config.html");
  }
);

When the user has chosen their options and closed the page, the “webviewclosed” event is fired. We will register another event listener to handle this. The data returned will be encoded in the URL as a JSON dictionary containing one element: “invert” which will have a value of either “on” or “off” depending on what the user chose. This is then assembled into an AppMessage and sent to the watch, which then sets and saves as appropriate:

Pebble.addEventListener("webviewclosed",
  function(e) {
    //Get JSON dictionary
    var configuration = JSON.parse(decodeURIComponent(e.response));
    console.log("Configuration window returned: " + JSON.stringify(configuration));

    //Send to Pebble, persist there
    Pebble.sendAppMessage(
      {"KEY_INVERT": configuration.invert},
      function(e) {
        console.log("Sending settings data...");
      },
      function(e) {
        console.log("Settings feedback failed!");
      }
    );
  }
);

That concludes the PebbleKit JS setup. Now for the last part – HTML!

Configuration HTML Page Setup
The final piece of the puzzle is the part the user will actually see and takes the form of a HTML page consisting of form elements such as checkboxes, selectors and buttons. We will just use one selector and one button to let the user choose if they want the watchapp to be inverted or not. Here’s the layout code:

<!DOCTYPE html>
<html>
  <head>
    <title>SDKTut9 Configuration</title>
  </head>
  <body>
    <h1>Pebble Config Tutorial</h1>
    <p>Choose watchapp settings</p>

    <p>Invert watchapp:
    <select id="invert_select">
      <option value="off">Off</option>
      <option value="on">On</option>
    </select>
    </p>

    <p>
    <button id="save_button">Save</button>
    </p>
  </body>
</html>

With this done we add a script to add a click listener to the button and a function to assemble the JSON option dictionary. This dictionary is then encoded into the URL and handed to the PebbleKit JS code to be sent to the watch in the “webviewclosed” event. Insert this into the HTML page:

<script>
  //Setup to allow easy adding more options later
  function saveOptions() {
    var invertSelect = document.getElementById("invert_select");

    var options = {
      "invert": invertSelect.options[invertSelect.selectedIndex].value
    }
    
    return options;
  };

  var submitButton = document.getElementById("save_button");
  submitButton.addEventListener("click", 
    function() {
      console.log("Submit");

      var options = saveOptions();
      var location = "pebblejs://close#" + encodeURIComponent(JSON.stringify(options));
      
      document.location = location;
    }, 
  false);
</script>

That completes the page that will get the user’s option choices and also the app itself! Compile the app and install on your watch. By choosing either ‘On’ or ‘Off’ on the configuration page you should be able to toggle the colour used in the watchapp. This should look like that shown below:

invert-notinvert

Conclusion

So, that’s the process I’ve adopted to set up app configuration. You can expand it by adding more AppMessage keys and more elements in the HTML page. Make sure to add the fields to the JSON object constructed in saveOptions() though.

As usual, the full code is available on GitHub.

Part 1: Linking Pebble and Spark Core

Introduction

In the last post (linked above) I detailed the basics of connecting a Pebble watchapp’s button clicks to a Spark.function() call on a Spark Core. In this post I will go over the reverse process: sending data back to the Pebble asynchronously. Once again this process uses a combination of Spark Cloud, PebbleKit JS and AppMessage to convey the message, which this time will be alerting a Pebble watch wearer that a button connected to the Core has been pressed via a short vibration pulse.

Preparing Pebble

The initial Pebble C program code is similar in structure to the last post’s starting point, but without any of the Click functionality, as this will be a receive-only app. Thus the start of your project’s main .c file will look like this:

#include <pebble.h>

#define KEY_BUTTON_STATE 0

static Window *window;
static TextLayer *text_layer;

static void window_load(Window *window) 
{
	//Create TextLayer
	text_layer = text_layer_create(GRect(0, 0, 144, 168));
	text_layer_set_text(text_layer, "Press button on Core pin D0");
	text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
	layer_add_child(window_get_root_layer(window), text_layer_get_layer(text_layer));
}

static void window_unload(Window *window) 
{
	//Destroy TextLayer
	text_layer_destroy(text_layer);
}

static void init(void) 
{
	//Create Window
	window = window_create();
	window_set_window_handlers(window, (WindowHandlers) {
		.load = window_load,
		.unload = window_unload,
	});

	//Prepare AppMessage
	app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());

	window_stack_push(window, true);
}

static void deinit(void) 
{
	//Destroy Window
	window_destroy(window);
}

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

Note that the name of the main AppMessage key has changed to a more appropriate KEY_BUTTON_STATE, but this is arbitrary – the value is still 0.

Instead of receiving button clicks, the app will be receiving messages sent from the phone on receiving a message from the Spark Cloud. To do this, we register an AppMessageInboxReceived handler before opening the service:

app_message_register_inbox_received((AppMessageInboxReceived) in_recv_handler);

and also declare the function above init():

static void in_recv_handler(DictionaryIterator *iterator, void *context)
{

}

This handler provides a DictionaryIterator structure that contains the received dictionary. To access the data, we use the dict_read_first() function to extract the tuple. This contains the key and value pair. We will then compare the value cstring and act accordingly (“HIGH” for button pressed and pulling pin D0 HIGH on the Core):

static void in_recv_handler(DictionaryIterator *iterator, void *context)
{
	//Get first tuple (should be KEY_BUTTON_STATE)
	Tuple *t = dict_read_first(iterator);

	//If it's there
	if(t)
	{
		if(strcmp("HIGH", t->value->cstring) == 0)
		{
			vibes_short_pulse();
		}
	}
}

Compile this and upload to your Pebble to make sure it is ready to work with PebbleKit JS, which we will set up next.

Preparing PebbleKit JS

Also similar to last time, we must setup the JS code to listen for events from the Spark Cloud and send AppMessages on to the watch. However, this time we do not require jQuery but instead use an object called EventSource that will provide the messages in a callback. This is done in the “ready” event handler:

Pebble.addEventListener("ready",
	function(e) {
		//Register EventSource listener
		var core = new EventSource("https://api.spark.io/v1/events/?access_token=" + accessToken);
		core.addEventListener("button_state", 
			function(response) {
				
			}, 
			false
		);

		console.log("Pebble JS Ready!");
	}
);

Note: This requires only your Access Token, not the Device ID.

Once this callback has been created, it will be executed whenever a Core firmware uses Spark.publish() with the topic “button_state”. When this event occurs, we will send the accompanying payload, either “HIGH” or “LOW” (details later) to the Pebble for it to decide whether to vibrate or not. This process looks like this:

core.addEventListener("button_state", 
	function(response) {
		//Interpret response as JSON
		var json = JSON.parse(response.data);

		console.log("Payload is '" + json.data + "'");

		//Send the payload
		Pebble.sendAppMessage(
			{"KEY_BUTTON_STATE":json.data},
			function(e) {
				console.log("Sent '" + json.data + "' to Pebble.");
			},
			function(e) {
				console.log("Failed to send data to Pebble!");
			}
		);
	}, 
	false
);

The AppMessage dictionary takes the form of a JSON dictionary with the key-value pair consisting of the declared key (remember to alter appinfo.json or the App Keys section in Settings on CloudPebble) and the word “HIGH” or “LOW” as received from the Core. We also get to register two callbacks for if the message was successful, and if it is not. The above code segment uses this to provide some meaningful log output.

This completes the setup of the JS segment of the message’s journey. With the JS code in place, re-compile and re-upload your Pebble .pbw file to your watch.

Preparing the Core

The last thing to do is to configure the Core to call Spark.publish() to notify the JS and C code we have already set up. This is done in the loop() function and takes the form of a simple if, else statement, depending on whether digitalRead(D0) determines whether the button is pressed. If you don’t have a button to hand, you can simulate one by simply touching the 3.3V pin of your core to D0 briefly once the following code is in place and uploaded:

static bool pressed = false;

void setup() {
    pinMode(D0, INPUT);
}

void loop() {
    //Publish button state
    if(digitalRead(D0) == HIGH && pressed == false)
    {
        Spark.publish("button_state", "HIGH");
        pressed = true;
        
        //Rate limit to prevent spamming the cloud
        delay(500);
    }
    else if(digitalRead(D0) == LOW && pressed == true)
    {
        Spark.publish("button_state", "LOW");
        pressed = false;
    }
}

If you do have a push button to hand, here is how to connect it up, as depicted on the Arduino site, except instead of pin 2, we are using Core pin D0. Once this is done, ensure both watchapp and Core firmware are uploaded and running before pressing the button. The watch should vibrate within a couple of seconds!

Conclusion

There we have an expansion on the original post, showing how to send asynchronous events and data from the Spare Core to the Pebble watch. A slight reduction in latency between the two can be theoretically achieved by calling app_comm_set_sniff_interval(SNIFF_INTERVAL_REDUCED), although this will consume more power over a long term period.

As always, the source code to this project can be found here on GitHub.

Enjoy!

Note: This post assumes basic knowledge of Pebble AppMessage, PebbleKit JS, jQuery $.ajax(), Spark.function() and similar API calls.

Introduction

A major appeal of the Pebble smartwatch is its potential both as a data display and a data input device. The addition of PebbleKit JS in SDK 2.0 allows a watchapp to connect to the internet and download data. Through the use of jQuery data can be requested, and with the EventSource object data can be listened for asynchronously.

This enables the watch to display data sent from the Core as well as make requests to the Spark Cloud to instruct the Core to execute functions or request the status of exposed variables. This means that the Pebble can use the Core as an interface for its I/O pins, which is an exciting prospect when considered with all the libraries available for Arduino (and by extension, the Core).

The purpose of this post is to instruct in what is required to get these two devices to interact. To do so, you must setup:

  • AppMessage and keys for the Pebble C program.
  • PebbleKit JS listeners (including jQuery and/or EventSource).
  • Use Spark.function(), Spark.variable() or Spark.publish() to expose the data you want to request/functions you want to execute remotely.

Visually, the process for triggering a Spark.function() call from Pebble looks like this (Spark.variable() works in the same way):

pebble-core-fuction

Prepare Pebble

To prepare the Pebble end, declare the keys you will be using for AppMessage communication. For this example, we will use a key called KEY_TOGGLE with a value of 0. This will be used to instruct PebbleKit JS to call a function registered on the Core with Spark.function() to toggle a pin HIGH or LOW. This is shown below in the starting template for the watchapp:

#include <pebble.h>

#define KEY_TOGGLE 0

static Window *window;
static TextLayer *text_layer;

static void select_click_handler(ClickRecognizerRef recognizer, void *context) 
{
  text_layer_set_text(text_layer, "Select");
}

static void click_config_provider(void *context) 
{
  window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}

static void window_load(Window *window) 
{
  //Create TextLayer
  text_layer = text_layer_create(GRect(0, 0, 144, 168));
  text_layer_set_text(text_layer, "Press SELECT to toggle Spark pin D0");
  text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(text_layer));
}

static void window_unload(Window *window) 
{
  //Destroy TextLayer
  text_layer_destroy(text_layer);
}

static void init(void) 
{
  //Create Window
  window = window_create();
  window_set_click_config_provider(window, click_config_provider);
  window_set_window_handlers(window, (WindowHandlers) {
    .load = window_load,
    .unload = window_unload,
  });
  window_stack_push(window, true);
}

static void deinit(void) 
{
  //Destroy Window
  window_destroy(window);
}

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

The next step is to declare this key in the Pebble app package when it is compiled. This is in appinfo.json (or Settings on CloudPebble):

"appKeys": {
  "KEY_TOGGLE": 0
}

Next, we open AppMessage in init():

//Prepare AppMessage
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());

create a function to send a key-value pair through AppMessage:

static void send_int(int key, int cmd)
{
  DictionaryIterator *iter;
  app_message_outbox_begin(&iter);
  
  Tuplet value = TupletInteger(key, cmd);
  dict_write_tuplet(iter, &value);
  
  app_message_outbox_send();
}

and add a call to send KEY_TOGGLE when the select button is pressed:

static void select_click_handler(ClickRecognizerRef recognizer, void *context) 
{
  send_int(KEY_TOGGLE, 0);  //Value can be any int for now
}

Prepare PebbleKit JS
After preparing the Pebble app to send an AppMessage, we must prepare PebbleKit JS to receive it and make a call to the Spark Cloud. The first stage in this is to initialise the pebble-js-app.js file like so:

var deviceId = "";
var accessToken = "";

Pebble.addEventListener("ready",
    function(e) {
        console.log("Pebble JS Ready!");
    }
);

Pebble.addEventListener("appmessage",
	function(dict) {
		console.log("AppMessage received!");
	}
);

The “appmessage” event callback is where we will make the Spark Cloud request, as this is triggered when an AppMessage is received. This will be run by any message received, but for the sake of precision and to accomodate multiple messages in an eventual application, we will single out messages with our KEY_TOGGLE key:

if(typeof dict.payload["KEY_TOGGLE"] !== "undefined") {
	console.log("KEY_TOGGLE received!");
}

It is in this clause that we will use jQuery to make the Spark Cloud request. First, we must include jQuery as it is not supported by default by PebbleKit JS (to the best of my knowledge!). We can do this by calling the following method in the “ready” event callback:

var importjQuery = function() {
	var script = document.createElement('script');
	script.src = 'http://code.jquery.com/jquery-latest.min.js';
	script.type = 'text/javascript';
	document.getElementsByTagName('head')[0].appendChild(script);
};

Pebble.addEventListener("ready",
    function(e) {
        importjQuery();
        console.log("Pebble JS Ready!");
    }
);

Next, we assemble the URL for the POST request and make the $.ajax() call. The URL contains the following elements (more details can be found on the Spark Docs site):

  • The base URL: https://api.spark.io/v1/devices/
  • The Core Device ID
  • The name of the function declared in Spark.function() (more on this later)
  • The Access Token for secure access for token holders
  • Any arguments (One string at this time)

Our function-to-be will be called int toggle(String args) as this is the accepted signature for Spark.function(). Storing our sensitive Device ID and Access Token as private variables in the JS file, the result looks like this:

var url = "https://api.spark.io/v1/devices/" + deviceId + "/toggle?access_token=" + accessToken;

//Send with jQuery
$.ajax({
  type: "POST",
  url: url,
  data: {"args":"none"},	//No args for the moment
  success: function() {
  	console.log("POST successful!");
  },
  dataType: "json"
});

Make sure you change the deviceId and accessToken variables at the top of the JS file to be those of you own Core!

This completes the PebbleKit JS preparation!

Prepare the Core
The final step in setting up a Spark.function() triggered by Pebble is to write the actual Core firmware. This is a very simple program. A function with the signature mentioned previously is created to do the toggling, with a bool variable to maintain state. This is then exposed to the Cloud API using Spark.function() in setup(). The end result looks like this:

bool is_high = false;

int toggle(String args)
{
    if(is_high == true)
    {
        digitalWrite(D0, LOW);
        is_high = false;
    }
    else
    {
        digitalWrite(D0, HIGH);
        is_high = true;
    }
    
    return 0;
}

void setup() {
    pinMode(D0, OUTPUT);
    Spark.function("toggle", toggle);
}

void loop() {
    //Nothing here
}

Finally, connect an LED to pin D0 of the Core. A recommended circuit is shown below (Using SchemeIT):

pebble-core-cir

Putting It All Together
Compile and upload the watchapp to your Pebble, compile and upload the Core firmware to your Core and launch the watchapp when the Core upload is done. You should see something similar to this:

pebble-spark-screen1

When the Core is breathing cyan and the Pebble watchapp is open, press the SELECT button. The LED should toggle on and off!

Conclusion
That’s a basic overview of the setup to enable the control of Spark Core pins (functions in general) from a Pebble. In the near future I’ll write more to cover sending data asynchronously back the other way using Spark.publish() and EventSource JS objects to receive them.

You can get the sample project code for all stages here on GitHub.

Any queries or feedback if I’ve made a JS faux-pas (I’m relatively new!), let me know!

Follow

Get every new post delivered to your Inbox.

Join 136 other followers