pebble development - using appmessage for two way communication
DESCRIPTION
This presentation introduces the AppMessage framework which is part of Pebble SDK. It allows developer to exchange messages between a watch and a phone with just a few APIs. Pebble SDK is available from http://developer.getpebble.comTRANSCRIPT
PebbleKitIntroducing 2-way communication
Available in Pebble Kit 1.1Download from developer.getpebble.comRequires Pebble firmware 1.10.2
New opportunities for developers!
•Watchfaces augmented with data from the internet• Remote controls for internet connected devices•Multi-player Pebble games• 4sq/Facebook/Yelp Check-in Apps• Sports/Weather/News/Traffic tickers• Emergency beacon activator• Deeper sports integration• Bitcoin price trackers
TWO-WAY COMMUNICATION OVERVIEW
Both sides can push updates
Updates are key-value pairs (dictionary)
{ /* icon */ 0: (uint8) 3, /* temp */ 1: “42°C”}
Key must be an integerValue can only be a string, bytes, int or int8
App-channels identified by a shared UUID
42c86ea4-1c3e-4a07-b889-2cccca914198
Acknowledgements provided by the framework
Not all apps require two-way communication
CURRENT LIMITATIONS
Only the watchapp in the foreground can send and receive notifications
Communication must be initiated by the phone
Only one 3rd-party app at a time
Apps need to be whitelisted by Pebble
USING TWO-WAY COMMUNICATION
Pebble
AppMessage PBWatch Intent Bus
AndroidiOS
AppMessage Generic updates exchange
static void s_main(void *params) { static PebbleAppHandlers s_handlers = { ... .messaging_info = { .buffer_sizes = { .inbound = 64, // inbound buffer size in bytes .outbound = 16, // outbound buffer size in bytes }, }, };
app_event_loop(params, &s_handlers);}
AppMessage
Declare in/out buffer sizes in your PebbleAppHandlers struct
static void s_main(void *params) { static PebbleAppHandlers s_handlers = { /* ... */ .messaging_info = { /* ... */ .default_callbacks.callbacks = { .out_sent = my_out_sent_handler, .out_failed = my_out_fail_handler, .in_received = my_in_rcv_handler, .in_dropped = my_in_drp_handler, }, }, };
app_event_loop(params, &s_handlers);}
Declare four new callbacks
AppMessage
void my_in_rcv_handler(DictionaryIterator *received, void *context) { // incoming message received}
void my_in_drp_handler(void *context, AppMessageResult reason) { // incoming message dropped}
void my_out_sent_handler(DictionaryIterator *sent, void *context) { // outgoing message was delivered}
void my_out_fail_handler(DictionaryIterator *failed, AppMessageResult reason, void *context) { // outgoing message failed}
Implement your callbacks
AppMessage
DictionaryIterator *iter;
// Reserve the output bufferapp_message_out_get(&iter);
// Fill the bufferdict_write_*(iter, ...);dict_write_end(iter);
// Send the messageapp_message_out_send();
// Release the output bufferapp_message_out_release();
Send messages
AppMessage
// Write data directlydict_write_data(iter, KEY_DATA, data, sizeof(data));dict_write_cstring(iter, KEY_STR, string);dict_write_int(iter, KEY_INT, value, sizeof(value) /*bytes*/, true /*signed*/);dict_write_uint8(iter, KEY_INT8, value8);
// Or use the Tuplet* constructsTuplet tuplet = TupletInteger(CMD_KEY, value);dict_write_tuplet(iter, &tuplet);
Writing into a dictionary
Dictionary
Tuple *tuple = dict_read_first(&iter);while (tuple) { switch (tuple->key) { case KEY_TO_A_DATA_VALUE: foo(tuple->value->data, tuple->length); break; case KEY_TO_A_STRING_VALUE: bar(tuple->value->cstring); break; case KEY_TO_A_INT32_VALUE: foobar(tuple->value->int32); break; case KEY_TO_A_UINT8_VALUE: foobar(tuple->value->uint8); break; } tuple = dict_read_next(&iter);}
Reading from a dictionary
Dictionary
dict_calc_buffer_size(2, sizeof(uint8_t), sizeof(char) * 8);
Evaluate the size of a dictionary
Dictionary
size = 1 + nTuples * 7 + Size1 + Size2 + ...
Evaluate the size of a dictionary
Dictionary
iOS Using AppMessage on iOS
[watch appMessagesGetIsSupported:^(PBWatch *watch, BOOL isAppMessagesSupported) { if (isAppMessagesSupported) { uint8_t bytes[] = {0x42, 0xc8, /* ... */, 0x91, 0x41, 0x98}; NSData *uuid = [NSData dataWithBytes:bytes length:sizeof(bytes)]; [watch appMessagesSetUUID:uuid]; }}];
Find out if the watch supports two-way communicationRegister your channel with the shared UUID
iOS
NSNumber *iconKey = @(0);NSNumber *temperatureKey = @(1);NSDictionary *update = @{ iconKey:[NSNumber numberWithUint8:weatherIconID], temperatureKey: @"42°C" };
[_targetWatch appMessagesPushUpdate:update onSent:^(PBWatch *watch, NSDictionary *update, NSError *error) { if (!error) { // :) } }];
Push updates to the watch
iOS
id updateHandler;
/* ... */
[ourWatch appMessagesGetIsSupported: ^(PBWatch *watch, BOOL isAppMessagesSupported) {
if(updateHandler) [ourWatch appMessagesRemoveUpdateHandler:updateHandler]; updateHandler = [watch appMessagesAddReceiveUpdateHandler: ^BOOL(PBWatch *watch, NSDictionary *update) { /* process message */ return YES; } ]; }];
Receive updates from the watch
iOS
Android Using AppMessage on Android
if (PebbleKit.areAppMessagesSupported()) { PebbleDictionary data = new PebbleDictionary(); data.addUint8(ICON_KEY, (byte) weatherIconId); data.addString(TEMP_KEY, "42°C");
PebbleKit.sendDataToPebble(getApplicationContext(), UUID, data);}
Find out if the watch supports two-way communicationPush an update to the watch
Android
public class MyDataReceiver extends PebbleDataReceiver { @Override public void receiveData(final Context context, final int transactionId, final PebbleDictionary data) { /* Process data */ PebbleKit.sendAckToPebble(getApplicationContext(), transactionId); }}
/* ... */
PebbleDataReceiver receiver = new PebbleDataReceiver(UUID);PebbleKit.registerReceivedDataHandler(getApplicationContext(), receiver);
Register to receive updatesYou must acknowledge updates
Android
Pebble
AppMessage PBWatch Intent Bus
AndroidiOS
AppSync
AppSync Synchronize watch fields
AppMessage
AppSync
A convenience layer on top of AppMessageMaintains and updates a Dictionary Provides a callback called whenever the Dictionary changes
static void s_main(void *params) { static PebbleAppHandlers s_handlers = { ... .messaging_info = { .buffer_sizes = { .inbound = 64, // inbound buffer size in bytes .outbound = 16, // outbound buffer size in bytes }, }, };
app_event_loop(params, &s_handlers);}
You still need to declare the input / output buffers
Tuplet initial_values[] = { TupletInteger(WEATHER_ICON_KEY, (uint8_t) 1), TupletCString(WEATHER_TEMPERATURE_KEY, "42°C"),};
Prepare the initial values of your data
#include <...>
AppSync sync;uint8_t sync_buffer[32];
Reserve global memory to store a AppSync structReserve global memory to store your dictionary
app_sync_init(&sync, sync_buffer, sizeof(sync_buffer), initial_values, ARRAY_LENGTH(initial_values), sync_tuple_changed_callback, sync_error_callback, NULL);
Initialize the synchronization
void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) { // Update your layers // Do not forget to call layer_mark_dirty()}
Process the first (and subsequent) update
Tuplet new_tuples[] = { TupletInteger(WEATHER_ICON_KEY, (uint8_t) 3), TupletCString(WEATHER_TEMPERATURE_KEY, "73°C"),};
app_sync_set(&sync, new_tuples, 2);
You can update the value on the Watch sideThe callback will be called when the app has acknowledged
Pebble
AppMessage PBWatch Intent Bus
AndroidiOS
AppSync
THANK YOU!@pebbledev
THANK YOU!@pebbledev