Pebble SDK 2.0 Tutorial #8: Android App Integration

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

Introduction

NOTE: This section requires knowledge on how to set up a new Android project in an IDE such as Eclipse! I will assume you are using Eclipse

After a few requests and comments, it’s time to revisit the Android app communication from the old 1.X tutorial series and produce an example app for the new 2.0 SDK.

For the purposes of simplicity, we will be extending the Pebble SDK new-project example, which starts us off with a nice button setup. To do this, create a new empty CloudPebble project, and add this code to the main .c file:

#include <pebble.h>

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 up_click_handler(ClickRecognizerRef recognizer, void *context) {
  text_layer_set_text(text_layer, "Up");
}

static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
  text_layer_set_text(text_layer, "Down");
}

static void click_config_provider(void *context) {
  window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
  window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
  window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
}

static void window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  text_layer = text_layer_create((GRect) { .origin = { 0, 72 }, .size = { bounds.size.w, 20 } });
  text_layer_set_text(text_layer, "Press a button");
  text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
  layer_add_child(window_layer, 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_click_config_provider(window, click_config_provider);
  window_set_window_handlers(window, (WindowHandlers) {
    .load = window_load,
    .unload = window_unload,
  });
  const bool animated = true;
  window_stack_push(window, animated);
}

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

int main(void) {
  init();

  APP_LOG(APP_LOG_LEVEL_DEBUG, "Done initializing, pushed window: %p", window);

  app_event_loop();
  deinit();
}

With that in place, test compilation to make sure all works as it should regarding button operation.

To extend this to interact with an Android app, we must first add in the AppMessage components from the AppMessage for PebbleKit JS section. First, define the in_received_handler() where received AppMessages will be interpreted as before:

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

After this, register the handler and open AppMessage inside init(), before pushing the Window:

//Register AppMessage events
app_message_register_inbox_received(in_received_handler);           
app_message_open(512, 512);    //Large input and output buffer sizes

Define globally the protocol we will use for communication using enumerations or by #defineing constants. I prefer enums, but both will do the job. We will define a key representing a button event occurring, and further values to distinguish between the buttons themselves:

enum {
	KEY_BUTTON_EVENT = 0,
	BUTTON_EVENT_UP = 1,
	BUTTON_EVENT_DOWN = 2,
	BUTTON_EVENT_SELECT = 3
};

The next step is to create a function to send these keys and values, which will be exactly the same as that shown in ‘AppMessage for PebbleKit JS’, above the click handlers:

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();
}

Finally, add calls to send_int() to each of the three button click handlers to send a signal corresponding to which button was pressed. This should look like the code shown below:

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

static void up_click_handler(ClickRecognizerRef recognizer, void *context) {
	text_layer_set_text(text_layer, "Up");
	send_int(KEY_BUTTON_EVENT, BUTTON_EVENT_UP);
}

static void down_click_handler(ClickRecognizerRef recognizer, void *context) {
	text_layer_set_text(text_layer, "Down");
	send_int(KEY_BUTTON_EVENT, BUTTON_EVENT_DOWN);
}

After setting up the Android side, we will come back to the Pebble side to implement the reverse process; sending data to the watch from Android.

Android App Integration

Set up a new Android project and make sure it runs correctly as just a blank Activity. Following the Android SDK plugin for Eclipse without modifying any of the settings except project location and name is a good starting point, which I will be using. After completing this process and removing the superfluous onCreateOptionsMenu(), my main Activity file looks like this:

package com.wordpress.ninedof.pebblesdk2part8;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

}

In order to communicate with Pebble, you will need to import the PebbleKit project into Eclipse. Once this is done, add it as a Library by right clicking the Tutorial project and choosing ‘Properties’, then clicking ‘Add’ under the ‘Android’ section. Choose ‘PEBBLE_KIT’ and click OK, then OK again to close the ‘Properties’ dialogue.

So, let’s make the two talk! As the messages will begin coming from the watch we must register a BroadcastReceiver to intercept the Pebble’s AppMessages. This is done as shown below:

private PebbleDataReceiver mReceiver;

... onCreate() here ...

@Override
protected void onResume() {
	super.onResume();

	mReceiver = new PebbleDataReceiver(UUID.fromString("2fc99a5d-ee35-4057-aa9b-0d4dd8e35ef5")) {

		@Override
		public void receiveData(Context context, int transactionId, PebbleDictionary data) {
			
		}

	};

	PebbleKit.registerReceivedDataHandler(this, mReceiver);
}

@Override
protected void onPause() {
	super.onPause();
	
	unregisterReceiver(mReceiver);
}

Be careful to note that the UUID specified in the constructor is the SAME UUID as specified in your corresponding watchapp’s appinfo.json, or in Settings on CloudPebble. The two must match for correct communication!

Next, define the exact same set of keys and values as on the Pebble side, as these are used to communicate:

private static final int
	KEY_BUTTON_EVENT = 0,
	BUTTON_EVENT_UP = 1,
	BUTTON_EVENT_DOWN = 2,
	BUTTON_EVENT_SELECT = 3;

Now this is done we add logic to the overridden receiveData() method to determine which button press was encoded in the received message:

@Override
public void receiveData(Context context, int transactionId, PebbleDictionary data) {
	//ACK the message
	PebbleKit.sendAckToPebble(context, transactionId);

	//Check the key exists
	if(data.getUnsignedInteger(KEY_BUTTON_EVENT) != null) {
		int button = data.getUnsignedInteger(KEY_BUTTON_EVENT).intValue();

		switch(button) {
		case BUTTON_EVENT_UP:
			//The UP button was pressed
			break;
		case BUTTON_EVENT_DOWN:
			//The DOWN button was pressed
			break;
		case BUTTON_EVENT_SELECT:
			//The SELECT button was pressed
			break;
		}
	}
}

The last step that completes this leg of the journey is to actually see which button was pressed on the Android display, akin to how it is on the Pebble. To do this, simply set the main View to a TextView in onCreate:

private TextView buttonView;

...

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	
	mButtonView = new TextView(this);
	mButtonView.setText("No button yet!");
	
	setContentView(mButtonView);
}

Finally, add calls to TextView.setText() in the switch statement within the receiveData method to show on the Android display which button was pressed:

switch(button) {
case BUTTON_EVENT_UP:
	//The UP button was pressed
	mButtonView.setText("UP button pressed!");
	break;
case BUTTON_EVENT_DOWN:
	//The DOWN button was pressed
	mButtonView.setText("DOWN button pressed!");
	break;
case BUTTON_EVENT_SELECT:
	//The SELECT button was pressed
	mButtonView.setText("SELECT button pressed!");
	break;
}

Time to try it out! Compile and install the watchapp, run the Android project in Eclipse to install and launch on your phone, open the watchapp and press a button. You should see something like this:

pebble-screenshot_2014-04-04_00-05-49

Screenshot_2014-04-04-00-04-54

Going The Other Way

To send data back to Pebble, we will define a new key on both sides to trigger a vibration. Name this key KEY_VIBRATION and give it a value of 4. With this done, modify the receiveData() method to send this message using a PebbleDictionary object after the switch statement like so:

//Make the watch vibrate
PebbleDictionary dict = new PebbleDictionary();
dict.addInt32(KEY_VIBRATION, 0);
PebbleKit.sendDataToPebble(context, UUID.fromString("2fc99a5d-ee35-4057-aa9b-0d4dd8e35ef5"), dict);

Finally, return to CloudPebble and add the new key to the main .c file. Finally, add a call to vibes_short_pulse() in in_received_handler():

enum {
	KEY_BUTTON_EVENT = 0,
	BUTTON_EVENT_UP = 1,
	BUTTON_EVENT_DOWN = 2,
	BUTTON_EVENT_SELECT = 3,
	KEY_VIBRATION = 4
};

...

static void in_received_handler(DictionaryIterator *iter, void *context) 
{
	Tuple *t = dict_read_first(iter);
	if(t)
	{
		vibes_short_pulse();
	}
}

Recompile, install and launch BOTH the Pebble and Android apps, press a button and feel the communications flowing through to your wrist!

Conclusions

Another long post! For more information on diving deeper and to send more complex forms of data, check out the AppMessage documentation.

Source code is on GitHub for both the Pebble and Android projects.

5 comments
  1. Hello,
    I would like to thank you for all those great tutorials. I almost did all of them, there where working perfectly.

    I have currently a problem to achieve this tutorial, I get from Android studio this error : cannot resolve method getUnsignedInteger(int).

    Do you have any idea of how I could solve this error?

    Ps: the link to my code https://github.com/Bleupi/tuto8/blob/master/app/src/main/java/com/gaucidallegmail/ambre/tuto8/MainActivity.java

    • bonsitm said:

      Hi, there has been a change in PebbleKit since I wrote this. See the official repo for the changes!

      • Belettemx said:

        Thanks! It works now !

Leave a reply to bonsitm Cancel reply