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 AppMessage
s 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 #define
ing constants. I prefer enum
s, 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:
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.
Pingback: Pebble SDK 2.0 Tutorial #9: App Configuration | try { work(); } finally { code(); }
Pingback: Pebble SDK 2.0 Tutorial #10: Event Services | try { work(); } finally { code(); }
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
Hi, there has been a change in PebbleKit since I wrote this. See the official repo for the changes!
Thanks! It works now !