Pebble Watch Face SDK Tutorial #6: 2 Way Communication with Android
Previous Tutorial Sections
All five previous parts of this tutorial series can be found on the ‘Pebble SDK Tutorial’ page link at the top of the page. Please read them if you are not already up to speed!
Introduction
One of the main selling points of the Pebble smartwatch is the fact that it can run apps that communicate with a companion app on an iPhone or Android smartphone. Indeed, it is impossible to get started without one. In the spirit of openness that is also a major selling point, this part of the tutorial series will describe how to accomplish such a task, enabling a much richer watch app experience.
Thanks to the remarkable amount of example code and documentation provided in the relatively early Pebble SDK v1.12, a lot of the code I will be using and showing here is derived from these examples. No need to fix what isn’t broken, and all that! They’ve done an excellent job on this one already.
As I have mentioned previously, I will only be covering the phone app side from an Android perspective, due to the fact I don’t own an iOS device or agree with Apple’s pricing policy to enter the ecosystem, but that’s just personal opinion.
It is assumed that the reader is already familiar with basic Android app development and the application lifecycle. If not, I could write an Android primer, which would in turn require a working knowledge of Java. We’ll see if that is required! Please let me know so I can gauge demand.
AppMessage system overview
On the Pebble platform, this is done using the AppMessage system. On the watch and on the phone, data is stored inside data structures called Dictionaries. When data is send or received, it is stored in data structures called Tuples. A Tuple contains a key and the data itself in what is known as a key-value pair. The key is a ‘label’ for that data and is constant.
For example, if you were writing an app to show the weather, you might have an int
to store the temperature. You create a key (number) to identify this data, and all these keys are the same on both the watch app and the phone app. Lets set the key for the temperature variable to a value of ‘5’. The phone sends a Dictionary consisting of a tuple with the temperature data contained within, with a key value of ‘5’. When this data arrives on the watch app, because the key identifying the temperature variable is the same as on the phone app, the Dictionary is searched for the tuple with key value ‘5’, and knows the accompanying data is the temperature to be displayed.
If that explanation leaves you wanting, here’s an diagram that will hopefully make it clearer:
Common App Features
In order to make sure that the phone app sends data to the correct watch app, both apps must use the same UUID to identify them. This has already been covered for the watch app, so here is how this is done on the phone app:
Here is how the phone app would store the keys. Remember that these should be identical to those declared on the watch app!
Watch App Specifics
On the watch app side of things, as predicted there is a use of handler functions to manage the following events:
- Send successful – AppMessage was sent successfully
- Send failed – AppMessage failed to send (or was not Acknowledged by phone app)
- Receive successful – AppMessage received from phone app
- Receive failed – AppMessage received but dropped due to lack of space or some other error
The first stage to implement this new functionality is to add the following lines to the PebbleAppHandlers in pbl_main()
:
Next, create the handler functions with matching names to those referenced in the ‘.message_info’ callbacks section in pbl_main() for each of the four callback scenarios in the bullet points above. Four blank handlers are shown below:
Sending data to the phone app
To send data to the phone app, a Dictionary is constructed and filled with the tuples describing the data to be sent, using the appropriate keys. Here is an example function that takes an int
and sends it to the phone app (adapted from the one provided in the SDK examples):
The argument sent here would be matched with a corresponding action on the phone app. In this example project (link to the source code will be at the end) each of the three buttons is given a key of 0, 1 or 2 (Select, Up, Down). When the up button is pressed, the watch app sends ‘1’ to the phone app. Because the keys are the same on both sides, when the phone app receives ‘1’ it can use that to perform the action the programmer wants to perform upon an up button press.
Receiving data from the watch on the phone
On the phone app side, the Dictionary is received in a callback method which is registered with the Android system when the app starts, in onResume()
:
Hopefully you can see that when the callback is triggered, the data is identified by the key, again it must correspond to the key on the watch app. This value is then used in a switch statement to decide what action to take. The phone app must first acknowledge (ACK) the data so the watch app doesn’t report a timeout.
Another important note is that the callback must be unregistered in onPause()
when the phone app is closed to prevent events being missed. Here is how that is done:
Sending data back to the watch app
Going back in the opposite direction, data is sent to the watch app by making use of a PebbleDictionary and key-value tuples. The methods used for this are provided by the PebbleKit SDK packages, which you can use and import by copying the packages into the ‘src’ folder of your Android project (shown here in Eclipse IDE for Java Developers):
The process for sending data from the phone app to the watch app is largely similar to the same process started from the watch app:
- Create the Dictionary (or PebbleDictionary in this case)
- Populate it with key-value tuples containing the data to be sent
- Send the Dictionary using the UUID (same as the watch app UUID)
This process is summarised in the image below, with comments for clarity:
Receiving data on the watch app
The final part of this process is receiving data from the phone app. This is done by (you guessed it!) extracting data from a Dictionary sent from the phone app and provided by the callback handler as an argument.
- A DictionaryIterator is used to search the Dictionary for data using the pre-defined key for that identifies the data received.
- This data is stored in a tuple
- The tuple contents can then be used as you wish
In the example below, the Dictionary is searched for the string data associated with the DATA_KEY, which was send from the phone app using the same key. This string is then displayed in a TextLayer for the user to see that the data has arrived successfully. Note that the Pebble OS automatically ACKs the received AppMessage:
Conclusion
So there you have it! Sending data from watch, receiving it on the phone, then sending data from the phone to the watch and using it. I’ll admit, this is the most complicated part of this tutorial series so far, and I hope to provide a bit more of a bridge from API Documentation to understanding the examples.
The example project source code can be found here!
Please feel free to post any questions here on in my Pebble Forum thread, and I’ll do my best to answer. If there is a lot of demand, I may write an Android primer too, but that’s probably another series in itself, and there are plenty of superb ones just a Google search away!
Hi,
First of all this would be my first pebble app(second actually , after the ‘Hello World’). I am a beginner in Android as well, but have managed to get some 4-5 Android apps working fine. I tried to get ur app workin on my pebble, but there seems to be few problems. Both the pebble side code and the android side code compiled/build well. I transferred the app to the pebble thru the HTTP server. The app’s name comes up as ‘Template App’ on the pebble. I opened the ‘Template App’ manually on the pebble, but after that nothing seems to work. I hope the first message ‘Ready’ should atleast pop up on the pebble , in my case e1 that isnt popping up .
The android side app just says ‘No button yet’.Please can you help me !
Thank you
Hi prajwal,
First off, are you using the source files linked at the end of the post? If you are, you should not be seeing ‘Template App’ but ‘AppMessage’ instead.
If not, the protocol expected by the watch app must be met by the Android app. Can you post a link to your source files?
Yes I am using those source files linked up here , but however I somehow managed to get the app name correctly on my watch and ur watch app is working half way fine. I mean the communication from the android app to the watch app is working. The watch app to the android app isnt working. Infact e1 if there was no communication from the pebble atleast there shud be some text on the pebble showing that the button was pressed right ?
Even that isnt working. Please help am struggling since may be 12-13hrs. Am right now on it actually 😉
From memory that is correct, some output should be shown. From looking at your files I can make the following comments:
1. Your string sent ‘can u see me’ will not be seen by the watch ever, as onCreate() is called before onResume(), where the data receiver is registered for the first time.
2. The watch app is likely not working because you haven’t actually registered your click config provider. See the docs here: . This should be done in handle_init().
Try these and report back!
here is a link to my files. Thank you
https://www.dropbox.com/sh/b1djjd82202i494/b2lpTVQgRh
woooooooowwwwwwwww 12-13hrs of sittin perplexed just for a line of code. Its working magically. I mean everything as it should. Thanx a tonne for the help.
P.S: The message ‘Can u c me’ is actually seen on my pebble. Please chk the dropbox link above, I have uploaded a snapshot of it. Also please let me know how it is displaying the message as you said it wudn.
Great to hear it’s working!
I was actually wrong before, as the message ‘can u c me’ will be delivered to the watch app regardless of whether an Android listener is in place.
Hi there, I tweet you for your email. I want to use only the up and down button. But in the sdk 2 it does not work. Can you help me please.
Thank you
Hi, I did DM you. I will be getting to AppMessage for SDK 2.0 in due course. What part does not work? Try the guide at the dev site in the mean time.
Thanks 🙂 I struggle with the “send key’s”
#include
Window* window;
TextLayer *text_layer;
//Set of enumerated keys with names and values. Identical to Android app keys
enum {
SELECT_KEY = 0x0, // TUPLE_INTEGER
UP_KEY = 0x01,
DOWN_KEY = 0x02,
DATA_KEY = 0x0
};
/**
* Handler for up click
*/
void up_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
//Send the UP_KEY
send_cmd(UP_KEY);
//Show it was sent on the watch app
text_layer_set_text(&textLayer, “FlashLight on!”);
}
/**
* Handler for down click
*/
void down_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
//Send the DOWN_KEY
send_cmd(DOWN_KEY);
//Show it was sent on the watch app
text_layer_set_text(&textLayer, “FlashLight off!”);
}
void window_load(Window *window)
{
//We will add the creation of the Window’s elements here soon!
text_layer = text_layer_create(GRect(0, 0, 144, 168));
text_layer_set_background_color(text_layer, GColorClear);
text_layer_set_text_color(text_layer, GColorBlack);
layer_add_child(window_get_root_layer(window), (Layer*) text_layer);
text_layer_set_text(text_layer, “Pebble FlashLight for Android!”);
}
void window_unload(Window *window)
{
//We will safely destroy the Window’s elements here!
text_layer_destroy(text_layer);
}
void init()
{
//Initialize the app elements here!
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
window_stack_push(window, true);
}
void deinit()
{
//De-initialize elements here to save memory!
window_destroy(window);
}
int main(void)
{
init();
app_event_loop();
deinit();
}
Hi Johnny, the send_cmd() function is not a feature of the Pebble SDK, but a wrapper derived by me. It is detailed in 1.X terms in the Tutorial section you mentioned, and accepts two arguments; the key and a value.
Have another look! You will need to include the full definition of send_cmd() in your C file and possibly make modifications for SDK 2.0. Or wait until I reach this stage in the 2.0 Tutorial series.
I can send you my files?
../src/main.c: In function ‘up_single_click_handler’:
../src/main.c:20:3: error: implicit declaration of function ‘send_cmd’ [-Werror=implicit-function-declaration]
../src/main.c:23:24: error: ‘textLayer’ undeclared (first use in this function)
../src/main.c:23:24: note: each undeclared identifier is reported only once for each function it appears in
../src/main.c: In function ‘down_single_click_handler’:
../src/main.c:34:24: error: ‘textLayer’ undeclared (first use in this function)
cc1: all warnings being treated as errors
Thank you! And thank you for the blog this really help!!
Thank you. Keep persevering!
Pingback: Pebble SDK 2.0 Tutorial #6: AppMessage for PebbleKit JS | try { work(); } finally { code(); }
hi sir, do you have the sample for 2 way communication with Android in version 2.x??
This sample can not be run in my cloud pebble because it is version 1.x..
Thank You.
is my coding below correct (version 2.x)
#include
///f1db3db2-0b0e-47c1-86af-e28d5ac7767c
#define UUID { 0xf1, 0xdb, 0x3d, 0xb2, 0x0b, 0x0e, 0×47, 0xc1, 0×86, 0xaf, 0xe2, 0x8d, 0x5a, 0xc7, 0×76, 0x7c }
Window* window;
TextLayer *text_layer;
InverterLayer *inv_layer;
char buffer[] = “00:00″;
enum {
STATUS_KEY = 0,
MESSAGE_KEY = 1
};
void send_message(void){
DictionaryIterator *iter;
app_message_outbox_begin(&iter);
dict_write_uint8(iter, STATUS_KEY, 0×1);
dict_write_cstring(iter, MESSAGE_KEY, “Hi Phone, I’m a Pebble!”);
dict_write_end(iter);
app_message_outbox_send();
}
void out_sent_handler(DictionaryIterator *sent, void *context) {
text_layer_set_text(text_layer, “Send successful!”);
}
static void out_failed_handler(DictionaryIterator *failed, AppMessageResult reason, void* context) {
text_layer_set_text(text_layer, “Send Failed”);
}
void back_click_handler(ClickRecognizerRef recognize, void *context)
{
vibes_long_pulse();
send_message();
/*DictionaryIterator *iter;
app_message_outbox_begin(&iter);
Tuplet value = TupletInteger(1, 42);
dict_write_tuplet(iter, &value);
app_message_outbox_send();*/
}
void click_config_provider(void *context)
{
window_multi_click_subscribe(BUTTON_ID_BACK, 3, 10, 1000, true, back_click_handler);
}
void tick_handler(struct tm *tick_time, TimeUnits units_changed)
{
//Format the buffer string using tick_time as the time source
strftime(buffer, sizeof(“00:00″), “%H:%M”, tick_time);
//Change the TextLayer text to show the new time!
text_layer_set_text(text_layer, buffer);
}
void window_load(Window *window)
{
//Time layer
text_layer = text_layer_create(GRect(0, 48, 132, 168));
text_layer_set_background_color(text_layer, GColorClear);
text_layer_set_text_color(text_layer, GColorBlack);
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
layer_add_child(window_get_root_layer(window), (Layer*) text_layer);
//Inverter layer
inv_layer = inverter_layer_create(GRect(0, 45, 144, 62));
layer_add_child(window_get_root_layer(window), (Layer*) inv_layer);
//Get a time structure so that the face doesn’t start blank
struct tm *t;
time_t temp;
temp = time(NULL);
t = localtime(&temp);
//Manually call the tick handler when the window is loading
tick_handler(t, MINUTE_UNIT);
}
void window_unload(Window *window)
{
//We will safely destroy the Window’s elements here!
text_layer_destroy(text_layer);
inverter_layer_destroy(inv_layer);
}
void init()
{
//Initialize the app elements here!
window = window_create();
window_set_window_handlers(window, (WindowHandlers) {
.load = window_load,
.unload = window_unload,
});
app_message_register_outbox_sent(out_sent_handler);
app_message_register_outbox_failed(out_failed_handler);
window_set_click_config_provider(window, click_config_provider);
tick_timer_service_subscribe(MINUTE_UNIT, (TickHandler) tick_handler);
window_stack_push(window, true);
app_message_open(512, 512);
}
void deinit()
{
tick_timer_service_unsubscribe();
window_destroy(window);
}
int main(void)
{
init();
app_event_loop();
deinit();
}
Looks okay on the Pebble side. Why are you using the BACK button? It is used by the system for exiting the app.
hmm… yup, but it’s okay..
it can be changed to any others button.
So, you mean my pebble side is okay, so do I need the .js file?
Is .js file necessary in my case?
I just want to trigger my mobile application from the pebble watch application by pressing the back button for several times.
Thank You.
The mobile application should implement a PebbleDataReceiver’s onReceive method to receive the messages. You should not need a JS file.
By the way, I downloaded the whole project you gave here.
And I changed the pebble coding into version 2 and I copy all the codes from the Android file into the Eclipse, and install into my phone.
However, it seems like no communication…..
How to solve the mobile application part?
Thank You.
Do the UUIDs match on both sides? This is essential.
ya, all the code are same as the one you given.
as shown below
package com.example.fyp;
import java.util.UUID;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.getpebble.android.kit.PebbleKit;
import com.getpebble.android.kit.util.PebbleDictionary;
public class MainActivity extends Activity {
//Keys – identical to watch app keys
private static final int DATA_KEY = 0;
private static final int SELECT_BUTTON_KEY = 0;
private static final int UP_BUTTON_KEY = 1;
private static final int DOWN_BUTTON_KEY = 2;
private static final int BUFFER_LENGTH = 32;
//Use the same UUID as on the watch
private static final UUID APP_UUID = UUID.fromString(“f1db3db2-0b0e-47c1-86af-e28d5ac7767c”);
private PebbleKit.PebbleDataReceiver dataHandler;
private TextView statusLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Allocate the TextView used to show which button press was received
//statusLabel = (TextView) findViewById(R.id.buttonLabel);
}
@Override
public void onResume() {
super.onResume();
//Start the companion app on the watch
PebbleKit.startAppOnPebble(getApplicationContext(), APP_UUID);
//Create a DataReciever
dataHandler = new PebbleKit.PebbleDataReceiver(APP_UUID) {
@Override
public void receiveData(final Context context, final int transactionId, final PebbleDictionary data) {
//ACK to prevent timeouts
PebbleKit.sendAckToPebble(context, transactionId);
//Get which button was pressed
int buttonPressed = data.getUnsignedInteger(DATA_KEY).intValue();
//Update UI
switch(buttonPressed) {
case SELECT_BUTTON_KEY: {
statusLabel.setText(“Select button pressed”);
sendStringToPebble(“Phone says ‘Select pressed!’”);
break;
}
case UP_BUTTON_KEY: {
statusLabel.setText(“Up button pressed”);
sendStringToPebble(“Phone says ‘Up pressed!’”);
break;
}
case DOWN_BUTTON_KEY: {
statusLabel.setText(“Down button pressed”);
sendStringToPebble(“Phone says ‘Down pressed!’”);
break;
}
default: {
statusLabel.setText(“Unknown button!”);
break;
}
}
}
};
//Register the DataHandler with Android to receive any messages from the watch
PebbleKit.registerReceivedDataHandler(getApplicationContext(), dataHandler);
}
@Override
public void onPause() {
super.onPause();
// Always deregister any Activity-scoped BroadcastReceivers when the Activity is paused
if (dataHandler != null) {
unregisterReceiver(dataHandler);
dataHandler = null;
}
}
/**
* Send a string to the companion Pebble Watch App
* @param message The string to send
*/
private void sendStringToPebble(String message) {
if(message.length() < BUFFER_LENGTH) {
//Create a PebbleDictionary
PebbleDictionary dictionary = new PebbleDictionary();
//Store a string in the dictionary using the correct key
dictionary.addString(DATA_KEY, message);
//Send the Dictionary
PebbleKit.sendDataToPebble(getApplicationContext(), APP_UUID, dictionary);
} else {
Log.i("sendStringToPebble()", "String too long!");
}
}
}
Ensure the UUID in appinfo.json matches the one in Eclipse.
sir, I do not have a .json file in my cloud pebble.
May i know how to create one?
Ah. CloudPebble manages this for you. You can find the UUID you must use in Eclipse under Settings then App UUID
I do not get it sir.
Besides pasting all the code you given into the Eclipse, is there any steps to do?
Change the UUID given in Eclipse where applicable to the one given in CloudPebble Settings
Okay sir, I have already change the UUID in Eclipse which same as the UUID given by the cloudPebble.
Does it now communicate?
Sir, I have changed both the UUID in the Eclipse and pebble given by the clouldPebble, but the mobile application still can not be triggered.
Do I need to install the mobile application into the pebble application or just install it into the phone?
Both apps must be installed onto their respective devices and the Android app must already be launched when the pebble button is pressed.
Yes, I already install the watch app into my pebble watch and I install the mobile application into my smartphone. However, my mobile application is not linked to the pebble mobile application.
So, how to link my mobile application to the pebble mobile application?
Thank You.
The two apps are linked by having the same UUID. When the watch app sends a message, the phone app uses the UUID as a filter to receive messages from only that one watchapp
It still can not be run, sir.
Do you have a simple code for Android and Pebble that (the pebble app will start the mobile application when the back button is pressed for several times whereby it does not need to send any message content.)
For the mobile application part, it does not need to receive any message content sent from pebble watch whereby it only have to start the application when the button is pressed on the pebbleWatch.
I do not know why my code cannot be run, could sir help me by giving the simple code to me?
Thank You.
Ah I understand what you want now. Launching an Android app is a more complex task than simply sending a message. Look into Android BroadcastReceivers and Services. An understanding of these is required to achieve this task.
Sorry sir, because i am new to Pebble and Android.
And I will have to submit the assignment of the Pebble (pressing button to trigger the mobile application) in 1 week time.
><
You have my sympathies! I am also closing on deadlines…
Pingback: Pebble SDK 2.0 Tutorial #8: Android App Integration | try { work(); } finally { code(); }
When i try the code (from the example project you put in the conclusion part), it gives me errors saying ‘pebble_os.h’ couldn’t find. When i delete them and try just to include classic ‘pebble.h’, it gives errors again saying PebbleAppHandlers etc. are not defined. I think it’s something to do with the versions. I am so confused. I would be extremely appreciated if someone help me. Thank you so much!
Make sure the first line of your C file is ‘#include ‘ and that you have a valid Pebble SDK installed