mirror of
https://github.com/google/pebble.git
synced 2025-03-25 21:09:05 +00:00
378 lines
14 KiB
Markdown
378 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: 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!";
|
||
|
}
|
||
|
}
|
||
|
```
|