mirror of
https://github.com/google/pebble.git
synced 2025-03-31 06:46:41 +00:00
437 lines
14 KiB
Markdown
437 lines
14 KiB
Markdown
---
|
|
# Copyright 2025 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
title: Sending and Receiving Data
|
|
description: |
|
|
How to send and receive data between your watchapp and phone.
|
|
guide_group: communication
|
|
order: 5
|
|
---
|
|
|
|
Before using ``AppMessage``, a Pebble C app must set up the buffers used for the
|
|
inbox and outbox. These are used to store received messages that have not yet
|
|
been processed, and sent messages that have not yet been transmitted. In
|
|
addition, callbacks may be registered to allow an app to respond to any success
|
|
or failure events that occur when dealing with messages. Doing all of this is
|
|
discussed in this guide.
|
|
|
|
|
|
## Message Structure
|
|
|
|
Every message sent or received using the ``AppMessage`` API is stored in a
|
|
``DictionaryIterator`` structure, which is essentially a list of ``Tuple``
|
|
objects. Each ``Tuple`` contains a key used to 'label' the value associated with
|
|
that key.
|
|
|
|
When a message is sent, a ``DictionaryIterator`` is filled with a ``Tuple`` for
|
|
each item of outgoing data. Conversely, when a message is received the
|
|
``DictionaryIterator`` provided by the callback is examined for the presence of
|
|
each key. If a key is present, the value associated with it can be read.
|
|
|
|
|
|
## Data Types
|
|
|
|
The [`Tuple.value`](``Tuple``) union allows multiple data types to be stored in
|
|
and read from each received message. These are detailed below:
|
|
|
|
| Name | Type | Size in Bytes | Signed? |
|
|
|------|------|---------------|---------|
|
|
| uint8 | `uint8_t` | 1 | No |
|
|
| uint16 | `uint16_t` | 2 | No |
|
|
| uint32 | `uint32_t` | 4 | No |
|
|
| int8 | `int8_t` | 1 | Yes |
|
|
| int16 | `int16_t` | 2 | Yes |
|
|
| int32 | `int32_t` | 4 | Yes |
|
|
| cstring | `char[]` | Variable length array | N/A |
|
|
| data | `uint8_t[]` | Variable length array | N/A |
|
|
|
|
|
|
## Buffer Sizes
|
|
|
|
The size of each of the outbox and inbox buffers must be set chosen such that
|
|
the largest message that the app will ever send or receive will fit. Incoming
|
|
messages that exceed the size of the inbox buffer, and outgoing messages that
|
|
exceed that size of the outbox buffer will be dropped.
|
|
|
|
These sizes are specified when the ``AppMessage`` system is 'opened', allowing
|
|
communication to occur:
|
|
|
|
```c
|
|
// Largest expected inbox and outbox message sizes
|
|
const uint32_t inbox_size = 64;
|
|
const uint32_t outbox_size = 256;
|
|
|
|
// Open AppMessage
|
|
app_message_open(inbox_size, outbox_size);
|
|
```
|
|
|
|
Each of these buffers is allocated at this moment, and comes out of the app's
|
|
memory budget, so the sizes of the inbox and outbox should be conservative.
|
|
Calculate the size of the buffer you require by summing the sizes of all the
|
|
keys and values in the larges message the app will handle. For example, a
|
|
message containing three integer keys and values will work with a 32 byte buffer
|
|
size.
|
|
|
|
|
|
## Choosing Keys
|
|
|
|
For each message sent and received, the contents are accessible using keys-value
|
|
pairs in a ``Tuple``. This allows each piece of data in the message to be
|
|
uniquely identifiable using its key, and also allows many different data types
|
|
to be stored inside a single message.
|
|
|
|
Each possible piece of data that may be transmitted should be assigned a unique
|
|
key value, used to read the associated value when it is found in a received
|
|
message. An example for a weather app is shown below::
|
|
|
|
* Temperature
|
|
* WindSpeed
|
|
* WindDirection
|
|
* RequestData
|
|
* LocationName
|
|
|
|
These values will be made available in any file that includes `pebble.h` prefixed
|
|
with `MESSAGE_KEY_`, such as `MESSAGE_KEY_Temperature` and `MESSAGE_KEY_WindSpeed`.
|
|
|
|
Examples of how these key values would be used in the phone-side app are
|
|
shown in {% guide_link communication/using-pebblekit-js %},
|
|
{% guide_link communication/using-pebblekit-ios %}, and
|
|
{% guide_link communication/using-pebblekit-android %}.
|
|
|
|
|
|
## Using Callbacks
|
|
|
|
Like many other aspects of the Pebble C API, the ``AppMessage`` system makes
|
|
use of developer-defined callbacks to allow an app to gracefully handle all
|
|
events that may occur, such as successfully sent or received messages as well as
|
|
any errors that may occur.
|
|
|
|
These callback types are discussed below. Each is used by first creating a
|
|
function that matches the signature of the callback type, and then registering
|
|
it with the ``AppMessage`` system to be called when that event type occurs. Good
|
|
use of callbacks to drive the app's UI will result in an improved user
|
|
experience, especially when errors occur that the user can be guided in fixing.
|
|
|
|
|
|
### Inbox Received
|
|
|
|
The ``AppMessageInboxReceived`` callback is called when a new message has been
|
|
received from the connected phone. This is the moment when the contents can be
|
|
read and used to drive what the app does next, using the provided
|
|
``DictionaryIterator`` to read the message. An example is shown below under
|
|
[*Reading an Incoming Message*](#reading-an-incoming-message):
|
|
|
|
```c
|
|
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
|
|
// A new message has been successfully received
|
|
|
|
}
|
|
```
|
|
|
|
Register this callback so that it is called at the appropriate time:
|
|
|
|
```c
|
|
// Register to be notified about inbox received events
|
|
app_message_register_inbox_received(inbox_received_callback);
|
|
```
|
|
|
|
|
|
### Inbox Dropped
|
|
|
|
The ``AppMessageInboxDropped`` callback is called when a message was received,
|
|
but it was dropped. A common cause of this is that the message was too big for
|
|
the inbox. The reason for failure can be determined using the
|
|
``AppMessageResult`` provided by the callback:
|
|
|
|
```c
|
|
static void inbox_dropped_callback(AppMessageResult reason, void *context) {
|
|
// A message was received, but had to be dropped
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped. Reason: %d", (int)reason);
|
|
}
|
|
```
|
|
|
|
Register this callback so that it is called at the appropriate time:
|
|
|
|
```c
|
|
// Register to be notified about inbox dropped events
|
|
app_message_register_inbox_dropped(inbox_dropped_callback);
|
|
```
|
|
|
|
|
|
### Outbox Sent
|
|
|
|
The ``AppMessageOutboxSent`` callback is called when a message sent from Pebble
|
|
has been successfully delivered to the connected phone. The provided
|
|
``DictionaryIterator`` can be optionally used to inspect the contents of the
|
|
message just sent.
|
|
|
|
> When sending multiple messages in a short space of time, it is **strongly**
|
|
> recommended to make use of this callback to wait until the previous message
|
|
> has been sent before sending the next.
|
|
|
|
```c
|
|
static void outbox_sent_callback(DictionaryIterator *iter, void *context) {
|
|
// The message just sent has been successfully delivered
|
|
|
|
}
|
|
```
|
|
|
|
Register this callback so that it is called at the appropriate time:
|
|
|
|
```c
|
|
// Register to be notified about outbox sent events
|
|
app_message_register_outbox_sent(outbox_sent_callback);
|
|
```
|
|
|
|
|
|
### Outbox Failed
|
|
|
|
The ``AppMessageOutboxFailed`` callback is called when a message just sent
|
|
failed to be successfully delivered to the connected phone. The reason can be
|
|
determined by reading the value of the provided ``AppMessageResult``, and the
|
|
contents of the failed message inspected with the provided
|
|
``DictionaryIterator``.
|
|
|
|
Use of this callback is strongly encouraged, since it allows an app to detect a
|
|
failed message and either retry its transmission, or inform the user of the
|
|
failure so that they can attempt their action again.
|
|
|
|
```c
|
|
static void outbox_failed_callback(DictionaryIterator *iter,
|
|
AppMessageResult reason, void *context) {
|
|
// The message just sent failed to be delivered
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Message send failed. Reason: %d", (int)reason);
|
|
}
|
|
```
|
|
|
|
Register this callback so that it is called at the appropriate time:
|
|
|
|
```c
|
|
// Register to be notified about outbox failed events
|
|
app_message_register_outbox_failed(outbox_failed_callback);
|
|
```
|
|
|
|
|
|
## Constructing an Outgoing Message
|
|
|
|
A message is constructed and sent from the C app via ``AppMessage`` using a
|
|
``DictionaryIterator`` object and the ``Dictionary`` APIs. Ensure that
|
|
``app_message_open()`` has been called before sending or receiving any messages.
|
|
|
|
The first step is to begin an outgoing message by preparing a
|
|
``DictionaryIterator`` pointer, used to keep track of the state of the
|
|
dictionary being constructed:
|
|
|
|
```c
|
|
// Declare the dictionary's iterator
|
|
DictionaryIterator *out_iter;
|
|
|
|
// Prepare the outbox buffer for this message
|
|
AppMessageResult result = app_message_outbox_begin(&out_iter);
|
|
```
|
|
|
|
The ``AppMessageResult`` should be checked to make sure the outbox was
|
|
successfully prepared:
|
|
|
|
```c
|
|
if(result == APP_MSG_OK) {
|
|
// Construct the message
|
|
|
|
} else {
|
|
// The outbox cannot be used right now
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result);
|
|
}
|
|
```
|
|
|
|
If the result is ``APP_MSG_OK``, the message construction can continue. Data is
|
|
now written to the dictionary according to data type using the ``Dictionary``
|
|
APIs. An example from the hypothetical weather app is shown below:
|
|
|
|
```c
|
|
if(result == APP_MSG_OK) {
|
|
// A dummy value
|
|
int value = 0;
|
|
|
|
// Add an item to ask for weather data
|
|
dict_write_int(out_iter, MESSAGE_KEY_RequestData, &value, sizeof(int), true);
|
|
}
|
|
```
|
|
|
|
After all desired data has been written to the dictionary, the message may be
|
|
sent:
|
|
|
|
```c
|
|
// Send this message
|
|
result = app_message_outbox_send();
|
|
|
|
// Check the result
|
|
if(result != APP_MSG_OK) {
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result);
|
|
}
|
|
```
|
|
|
|
<div class="alert alert--fg-white alert--bg-dark-red">
|
|
{% markdown %}
|
|
**Important**
|
|
|
|
Any app that wishes to send data from the watch to the phone via PebbleKit JS
|
|
must wait until the `ready` event has occured, indicating that the phone has
|
|
loaded the JavaScript for the app and it is ready to receive data. See
|
|
[*Advanced Communication*](/guides/communication/advanced-communication#waiting-for-pebblekit-js)
|
|
for more information.
|
|
{% endmarkdown %}
|
|
</div>
|
|
|
|
|
|
Once the message send operation has been completed, either the
|
|
``AppMessageOutboxSent`` or ``AppMessageOutboxFailed`` callbacks will be called
|
|
(if they have been registered), depending on either a success or failure
|
|
outcome.
|
|
|
|
|
|
### Example Outgoing Message Construction
|
|
|
|
A complete example of assembling an outgoing message is shown below:
|
|
|
|
```c
|
|
// Declare the dictionary's iterator
|
|
DictionaryIterator *out_iter;
|
|
|
|
// Prepare the outbox buffer for this message
|
|
AppMessageResult result = app_message_outbox_begin(&out_iter);
|
|
if(result == APP_MSG_OK) {
|
|
// Add an item to ask for weather data
|
|
int value = 0;
|
|
dict_write_int(out_iter, MESSAGE_KEY_RequestData, &value, sizeof(int), true);
|
|
|
|
// Send this message
|
|
result = app_message_outbox_send();
|
|
if(result != APP_MSG_OK) {
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending the outbox: %d", (int)result);
|
|
}
|
|
} else {
|
|
// The outbox cannot be used right now
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Error preparing the outbox: %d", (int)result);
|
|
}
|
|
```
|
|
|
|
|
|
## Reading an Incoming Message
|
|
|
|
When a message is received from the connected phone the
|
|
``AppMessageInboxReceived`` callback is called, and the message's contents can
|
|
be read using the provided ``DictionaryIterator``. This should be done by
|
|
looking for the presence of each expectd `Tuple` key value, and using the
|
|
associated value as required.
|
|
|
|
Most apps will deal with integer values or strings to pass signals or some
|
|
human-readable information respectively. These common use cases are discussed
|
|
below.
|
|
|
|
|
|
### Reading an Integer
|
|
|
|
**From JS**
|
|
|
|
```js
|
|
var dict = {
|
|
'Temperature': 29
|
|
};
|
|
```
|
|
|
|
**In C**
|
|
|
|
```c
|
|
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
|
|
// A new message has been successfully received
|
|
|
|
// Does this message contain a temperature value?
|
|
Tuple *temperature_tuple = dict_find(iter, MESSAGE_KEY_Temperature);
|
|
if(temperature_tuple) {
|
|
// This value was stored as JS Number, which is stored here as int32_t
|
|
int32_t temperature = temperature_tuple->value->int32;
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
### Reading a String
|
|
|
|
A common use of transmitted strings is to display them in a ``TextLayer``. Since
|
|
the displayed text is required to be long-lived, a `static` `char` buffer can be
|
|
used when the data is received:
|
|
|
|
**From JS**
|
|
|
|
```js
|
|
var dict = {
|
|
'LocationName': 'London, UK'
|
|
};
|
|
```
|
|
|
|
**In C**
|
|
|
|
```c
|
|
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
|
|
// Is the location name inside this message?
|
|
Tuple *location_tuple = dict_find(iter, MESSAGE_KEY_LocationName);
|
|
if(location_tuple) {
|
|
// This value was stored as JS String, which is stored here as a char string
|
|
char *location_name = location_tuple->value->cstring;
|
|
|
|
// Use a static buffer to store the string for display
|
|
static char s_buffer[MAX_LENGTH];
|
|
snprintf(s_buffer, sizeof(s_buffer), "Location: %s", location_name);
|
|
|
|
// Display in the TextLayer
|
|
text_layer_set_text(s_text_layer, s_buffer);
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
### Reading Binary Data
|
|
|
|
Apps that deal in packed binary data can send this data and pack/unpack as
|
|
required on either side:
|
|
|
|
**From JS**
|
|
|
|
```js
|
|
var dict = {
|
|
'Data': [1, 2, 4, 8, 16, 32, 64]
|
|
};
|
|
```
|
|
|
|
**In C**
|
|
|
|
```c
|
|
static void inbox_received_callback(DictionaryIterator *iter, void *context) {
|
|
// Expected length of the binary data
|
|
const int length = 32;
|
|
|
|
// Does this message contain the data tuple?
|
|
Tuple *data_tuple = dict_find(iter, MESSAGE_KEY_Data);
|
|
if(data_tuple) {
|
|
// Read the binary data value
|
|
uint8_t *data = data_tuple->value->data;
|
|
|
|
// Inspect the first byte, for example
|
|
uint8_t byte_zero = data[0];
|
|
|
|
// Store into an app-defined buffer
|
|
memcpy(s_buffer, data, length);
|
|
}
|
|
```
|