pebble/devsite/source/_guides/smartstraps/talking-to-smartstraps.md
2025-02-24 18:58:29 -08:00

14 KiB

title description guide_group order platforms related_docs
Talking To Smartstraps Information on how to use the Pebble C API to talk to a connected smartstrap. smartstraps 3
basalt
chalk
diorite
emery
Smartstrap

To talk to a connected smartstrap, the Smartstrap API is used to establish a connection and exchange arbitrary data. The exchange protocol is specified in {% guide_link smartstraps/smartstrap-protocol %} and most of it is abstracted by the SDK. This also includes handling of the {% guide_link smartstraps/smartstrap-protocol#link-control-profile "Link Control Profile" %}.

Read {% guide_link smartstraps/talking-to-pebble %} to learn how to use an example library for popular Arduino microcontrollers to implement the smartstrap side of the protocol.

Note: Apps running on multiple hardware platforms that may or may not include a smartstrap connector should use the PBL_SMARTSTRAP compile-time define (as well as checking API return values) to gracefully handle when it is not available.

Services and Attributes

Generic Service Profile

The Pebble smartstrap protocol uses the concept of 'services' and 'attributes' to organize the exchange of data between the watch and the smartstrap. Services are identified by a 16-bit number. Some of these service identifiers have a specific meaning; developers should read {% guide_link smartstraps/smartstrap-protocol#supported-services-and-attributes "Supported Services and Attributes" %} for a complete list of reserved service IDs and ranges of service IDs that can be used for experimentation.

Attributes are also identified by a 16-bit number. The meaning of attribute values is specific to the service of that attribute. The smartstrap protocol defines the list of attributes for some services, but developers are free to define their own list of attributes in their own services.

This abstraction supports read and write operations on any attribute as well as sending notifications from the strap when an attribute value changes. This is called the Generic Service Profile and is the recommended way to exchange data with smartstraps.

Raw Data Service

Developers can also choose to use the Raw Data Service to minimize the overhead associated with transmitting data. To use this profile a Pebble developer will use the same APIs described in this guide with the service ID and attribute ID set to SMARTSTRAP_RAW_DATA_SERVICE_ID and SMARTSTRAP_RAW_DATA_ATTRIBUTE_ID SDK constants respectively.

Manipulating Attributes

The Smartstrap API uses the SmartstrapAttribute type as a proxy for an attribute on the smartstrap. It includes the service ID of the attribute, the ID of the attribute itself, as well as a data buffer that is used to store the latest read or written value of the attribute.

Before you can read or write an attribute, you need to initialize a SmartstrapAttribute that will be used as a proxy for the attribute on the smartstrap. The first step developers should take is to decide upon and define their services and attributes:

// Define constants for your service ID, attribute ID 
// and buffer size of your attribute.
static const SmartstrapServiceId s_service_id = 0x1001;
static const SmartstrapAttributeId s_attribute_id = 0x0001;
static const int s_buffer_length = 64;

Then, define the attribute globally:

// Declare an attribute pointer
static SmartstrapAttribute *s_attribute;

Lastly create the attribute during app initialization, allocating its buffer:

// Create the attribute, and allocate a buffer for its data
s_attribute = smartstrap_attribute_create(s_service_id, s_attribute_id, 
                                                            s_buffer_length);

Later on, APIs such as smartstrap_attribute_get_service_id() and smartstrap_attribute_get_attribute_id() can be used to confirm these values for any SmartstrapAttribute created previously. This is useful if an app deals with more than one service or attribute.

Attributes can also be destroyed when an app is exiting or no longer requires them by using smartstrap_attribute_destroy():

// Destroy this attribute
smartstrap_attribute_destroy(s_attribute);

Connecting to a Smartstrap

The first thing a smartstrap-enabled app should do is call smartstrap_subscribe() to register the handler functions (described below) that will be called when smartstrap-related events occur. Such events can be one of four types.

The SmartstrapServiceAvailabilityHandler handler, used when a smartstrap reports that a service is available, or has become unavailable.

static void strap_availability_handler(SmartstrapServiceId service_id,
                                       bool is_available) {
  // A service's availability has changed
  APP_LOG(APP_LOG_LEVEL_INFO, "Service %d is %s available",
                                (int)service_id, is_available ? "now" : "NOT");
}

See below under Writing Data and Reading Data for explanations of the other callback types.

With all four of these handlers in place, the subscription to the associated events can be registered.

// Subscribe to the smartstrap events
smartstrap_subscribe((SmartstrapHandlers) {
  .availability_did_change = strap_availability_handler,
  .did_read = strap_read_handler,
  .did_write = strap_write_handler,
  .notified = strap_notify_handler
});

As with the other [Event Services](Event Service), the subscription can be removed at any time:

// Stop getting callbacks
smartstrap_unsubscribe();

The availability of a service can be queried at any time:

if(smartstrap_service_is_available(s_service_id)) {
  // Our service is available!

} else {
  // Our service is not currently available, handle gracefully
  APP_LOG(APP_LOG_LEVEL_ERROR, "Service %d is not available.", (int)s_service_id);
}

Writing Data

The smartstrap communication model (detailed under {% guide_link smartstraps/smartstrap-protocol#communication-model "Communication Model" %}) uses the master-slave principle. This one-way relationship means that Pebble can request data from the smartstrap at any time, but the smartstrap cannot. However, the smartstrap may notify the watch that data is waiting to be read so that the watch can read that data at the next opportunity.

To send data to a smartstrap an app must call smartstrap_attribute_begin_write() which will return a buffer to write into. When the app is done preparing the data to be sent in the buffer, it calls smartstrap_attribute_end_write() to actually send the data.

// Pointer to the attribute buffer
size_t buff_size;
uint8_t *buffer;

// Begin the write request, getting the buffer and its length
smartstrap_attribute_begin_write(attribute, &buffer, &buff_size);

// Store the data to be written to this attribute
snprintf((char*)buffer, buff_size, "Hello, smartstrap!");

// End the write request, and send the data, not expecting a response
smartstrap_attribute_end_write(attribute, buff_size, false);

Another message cannot be sent until the strap responds (a did_write callback for Write requests, or did_read for Read/Write+Read requests) or the timeout expires. Doing so will cause the API to return SmartstrapResultBusy. Read {% guide_link smartstraps/talking-to-smartstraps#timeouts "Timeouts" %} for more information on smartstrap timeouts.

The SmartstrapWriteHandler will be called when the smartstrap has acknowledged the write operation (if using the Raw Data Service, there is no acknowledgement and the callback will be called when Pebble is done sending the frame to the smartstrap). If a read is requested (with the request_read parameter of smartstrap_attribute_end_write()) then the read callback will also be called when the smartstrap sends the attribute value.

static void strap_write_handler(SmartstrapAttribute *attribute,
                                SmartstrapResult result) {
  // A write operation has been attempted
  if(result != SmartstrapResultOk) {
    // Handle the failure
    APP_LOG(APP_LOG_LEVEL_ERROR, "Smartstrap error occured: %s",
                                          smartstrap_result_to_string(result));
  }
}

If a timeout occurs on a non-raw-data write request (with the request_read parameter set to false), SmartstrapResultTimeOut will be passed to the did_write handler on the watch side.

Reading Data

The simplest way to trigger a read request is to call smartstrap_attribute_read(). Another way to trigger a read is to set the request_read parameter of smartstrap_attribute_end_write() to true. In both cases, the response will be received asynchronously and the SmartstrapReadHandler will be called when it is received.

static void strap_read_handler(SmartstrapAttribute *attribute,
                               SmartstrapResult result, const uint8_t *data,
                               size_t length) {
  if(result == SmartstrapResultOk) {
    // Data has been read into the data buffer provided
    APP_LOG(APP_LOG_LEVEL_INFO, "Smartstrap sent: %s", (char*)data);
  } else {
    // Some error has occured
    APP_LOG(APP_LOG_LEVEL_ERROR, "Error in read handler: %d", (int)result);
  }
}

static void read_attribute() {
  SmartstrapResult result = smartstrap_attribute_read(attribute);
  if(result != SmartstrapResultOk) {
    APP_LOG(APP_LOG_LEVEL_ERROR, "Error reading attribute: %s",
                                        smartstrap_result_to_string(result));
  }
}

Note: smartstrap_attribute_begin_write() may not be called within a did_read handler (SmartstrapResultBusy will be returned).

Similar to write requests, if a timeout occurs when making a read request the did_read handler will be called with SmartstrapResultTimeOut passed in the result parameter.

Receiving Notifications

To save as much power as possible, the notification mechanism can be used by the smartstrap to alert the watch when there is data that requires processing. When this happens, the SmartstrapNotifyHandler handler is called with the appropriate attribute provided. Developers can use this mechanism to allow the watch to sleep until it is time to read data from the smartstrap, or simply as a messsaging mechanism.

static void strap_notify_handler(SmartstrapAttribute *attribute) {
  // The smartstrap has emitted a notification for this attribute
  APP_LOG(APP_LOG_LEVEL_INFO, "Attribute with ID %d sent notification",
                        (int)smartstrap_attribute_get_attribute_id(attribute));

  // Some data is ready, let's read it
  smartstrap_attribute_read(attribute);
}

Callbacks For Each Type of Request

There are a few different scenarios that involve the SmartstrapReadHandler and SmartstrapWriteHandler, where the callbacks to these SmartstrapHandlers will change depending on the type of request made by the watch.

Request Type Callback Sequence
Read only did_write when the request is sent. did_read when the response arrives or an error (e.g.: a timeout) occurs.
Write+Read request did_write when the request is sent. did_read when the response arrives or an error (e.g.: a timeout) occurs.
Write (Raw Data Service) did_write when the request is sent.
Write (any other service) did_write when the write request is acknowledged by the smartstrap.

For Write requests only, did_write will be called when the attribute is ready for another request, and for Reads/Write+Read requests did_read will be called when the attribute is ready for another request.

Timeouts

Read requests and write requests to an attribute expect a response from the smartstrap and will generate a timeout error if the strap does not respond before the expiry of the timeout.

The maximum timeout value supported is 1000ms, with the default value SMARTSTRAP_TIMEOUT_DEFAULT of 250ms. A smaller or larger value can be specified by the developer:

// Set a timeout of 500ms
smartstrap_set_timeout(500);

Smartstrap Results

When data is sent to the smartstrap, one of several results is possible. These are returned by various API functions (such as smartstrap_attribute_read()), and are enumerated as follows:

Result Value Description
SmartstrapResultOk 0 No error occured.
SmartstrapResultInvalidArgs 1 The arguments provided were invalid.
SmartstrapResultNotPresent 2 The Smartstrap port is not present on this watch.
SmartstrapResultBusy 3 The connection is currently busy. For example, this can happen if the watch is waiting for a response from the smartstrap.
SmartstrapResultServiceUnavailable 4 Either a smartstrap is not connected or the connected smartstrap does not support the specified service.
SmartstrapResultAttributeUnsupported 5 The smartstrap reported that it does not support the requested attribute.
SmartstrapResultTimeOut 6 A timeout occured during the request.

The function shown below returns a human-readable string for each value, useful for debugging.

static char* smartstrap_result_to_string(SmartstrapResult result) {
  switch(result) {
    case SmartstrapResultOk:
      return "SmartstrapResultOk";
    case SmartstrapResultInvalidArgs:
      return "SmartstrapResultInvalidArgs";
    case SmartstrapResultNotPresent:
      return "SmartstrapResultNotPresent";
    case SmartstrapResultBusy:
      return "SmartstrapResultBusy";
    case SmartstrapResultServiceUnavailable:
      return "SmartstrapResultServiceUnavailable";
    case SmartstrapResultAttributeUnsupported:
      return "SmartstrapResultAttributeUnsupported";
    case SmartstrapResultTimeOut:
      return "SmartstrapResultTimeOut";
    default:
      return "Not a SmartstrapResult value!";
  }
}