--- # 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: Talking To Smartstraps description: | Information on how to use the Pebble C API to talk to a connected smartstrap. guide_group: smartstraps order: 3 platforms: - basalt - chalk - diorite - emery related_docs: - 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: ```c // 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: ```c // Declare an attribute pointer static SmartstrapAttribute *s_attribute; ``` Lastly create the attribute during app initialization, allocating its buffer: ```c // 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()``: ```c // 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. ```c 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*](#writing-data) and [*Reading Data*](#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. ```c // 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: ```c // Stop getting callbacks smartstrap_unsubscribe(); ``` The availability of a service can be queried at any time: ```c 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. ```c // 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. ```c 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. ```c 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. ```c 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: ```c // 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. ```c 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!"; } } ```