mirror of
https://github.com/google/pebble.git
synced 2025-06-02 08:23:10 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
761
src/fw/applib/bluetooth/ble_ad_parse.c
Normal file
761
src/fw/applib/bluetooth/ble_ad_parse.c
Normal file
|
@ -0,0 +1,761 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_ad_parse.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "util/math.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <btutil/bt_uuid.h>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parsed advertisement data structures.
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! AD TYPE Values as specified by the Bluetooth 4.0 Spec.
|
||||
//! See "Appendix C (Normative): EIR and AD Formats" in Core_v4.0.pdf
|
||||
typedef enum {
|
||||
BLEAdTypeFlags = 0x01,
|
||||
BLEAdTypeService16BitUUIDPartial = 0x02,
|
||||
BLEAdTypeService16BitUUIDComplete = 0x03,
|
||||
BLEAdTypeService32BitUUIDPartial = 0x04,
|
||||
BLEAdTypeService32BitUUIDComplete = 0x05,
|
||||
BLEAdTypeService128BitUUIDPartial = 0x06,
|
||||
BLEAdTypeService128BitUUIDComplete = 0x07,
|
||||
|
||||
BLEAdTypeLocalNameShortened = 0x08,
|
||||
BLEAdTypeLocalNameComplete = 0x09,
|
||||
|
||||
BLEAdTypeTxPowerLevel = 0x0a,
|
||||
|
||||
BLEAdTypeManufacturerSpecific = 0xff,
|
||||
} BLEAdType;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! AD DATA element header
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
uint8_t length;
|
||||
BLEAdType type:8;
|
||||
} BLEAdElementHeader;
|
||||
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
BLEAdElementHeader header;
|
||||
uint8_t data[];
|
||||
} BLEAdElement;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Consuming BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a parsed Service UUIDs element.
|
||||
//! @param uuids The array with Service UUIDs
|
||||
//! @param count The number of Service UUIDs the array contains
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseServicesCallback)(const Uuid uuids[], uint8_t count,
|
||||
void *cb_data);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a parsed Local Name element.
|
||||
//! @param local_name_bytes This is a *NON* zero terminated UTF-8 string.
|
||||
//! @param length The length of local_name_bytes
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseLocalNameCallback)(const uint8_t *local_name_bytes,
|
||||
uint8_t length, void *cb_data);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a parsed TX Power Level element.
|
||||
//! @param tx_power_level The TX Power Level value.
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseTXPowerLevelCallback)(int8_t tx_power_level,
|
||||
void *cb_data);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal parser callback. Gets called for a Manufacturer Specific data elem.
|
||||
//! @param company_id The Company ID
|
||||
//! @param data The Manufacturer Specific data
|
||||
//! @param length The length in bytes of data
|
||||
//! @param cb_data Pointer to arbitrary client data as passed to the parse call.
|
||||
//! @return true to continue parsing or false to stop after returning.
|
||||
typedef bool (*BLEAdParseManufacturerSpecificCallback)(uint16_t company_id,
|
||||
const uint8_t *data,
|
||||
uint8_t length,
|
||||
void *cb_data);
|
||||
|
||||
typedef struct {
|
||||
BLEAdParseServicesCallback services_cb;
|
||||
BLEAdParseLocalNameCallback local_name_cb;
|
||||
BLEAdParseTXPowerLevelCallback tx_power_level_cb;
|
||||
BLEAdParseManufacturerSpecificCallback manufacturer_cb;
|
||||
} BLEAdParseCallbacks;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for Services List data elements.
|
||||
static bool parse_services_list(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
const uint8_t uuid_type = elem->header.type / 2;
|
||||
// Most common is probably 128-bit UUID, then 16-bit, then 32-bit:
|
||||
const size_t uuid_width = (uuid_type == 3) ? 16 : ((uuid_type == 1) ? 2 : 4);
|
||||
const size_t num_uuids = (elem->header.length - 1 /* Type byte */)
|
||||
/ uuid_width;
|
||||
|
||||
if (!num_uuids) {
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
Uuid uuids[num_uuids];
|
||||
|
||||
// Iterate through the list, expanding each UUID to 128-bit equivalents,
|
||||
// then copying them into the uuids[] array:
|
||||
const uint8_t *uuid_data = elem->data;
|
||||
for (size_t i = 0; i < num_uuids; ++i) {
|
||||
switch (uuid_type) {
|
||||
case 1: { // 16 bit
|
||||
uint16_t u16 = *(uint16_t *) uuid_data;
|
||||
uuids[i] = bt_uuid_expand_16bit(u16);
|
||||
uuid_data += sizeof(uint16_t);
|
||||
break;
|
||||
}
|
||||
case 2: { // 32 bit
|
||||
uint32_t u32 = *(uint32_t *) uuid_data;
|
||||
uuids[i] = bt_uuid_expand_32bit(u32);
|
||||
uuid_data += sizeof(uint32_t);
|
||||
break;
|
||||
}
|
||||
case 3: // 128-bit
|
||||
uuids[i] = UuidMakeFromLEBytes(uuid_data);
|
||||
uuid_data += sizeof(Uuid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Call back to client with parsed data:
|
||||
return callbacks->services_cb(uuids, num_uuids, cb_data);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for Local Name data element.
|
||||
static bool parse_local_name(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
// Length of the raw string:
|
||||
const uint8_t raw_length = elem->header.length - 1 /* -1 Type byte */;
|
||||
if (!raw_length) {
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
// Call back to client with parsed data:
|
||||
return callbacks->local_name_cb(elem->data, raw_length, cb_data);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for TX Power Level data element.
|
||||
static bool parse_power_level(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
if (elem->header.length != 2) {
|
||||
// In case the length is not what it should be, do not add data element.
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
const int8_t tx_power_level = *(int8_t *)elem->data;
|
||||
|
||||
// Call back to client with parsed data:
|
||||
return callbacks->tx_power_level_cb(tx_power_level, cb_data);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Parser for Manufacturer Specific data element.
|
||||
|
||||
static bool parse_manufact_spec(const BLEAdElement *elem,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
|
||||
if (elem->header.length < 3) {
|
||||
// The first 2 octets should be the Company Identifier Code
|
||||
// (+1 for Type byte)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (callbacks->manufacturer_cb) {
|
||||
// Little-endian:
|
||||
const uint16_t *company_id = (uint16_t *) elem->data;
|
||||
|
||||
// Call back to client with parsed data:
|
||||
const uint8_t manufacturer_data_size =
|
||||
elem->header.length - sizeof(*company_id) - 1 /* -1 Type Byte */;
|
||||
return callbacks->manufacturer_cb(ltohs(*company_id),
|
||||
elem->data + sizeof(*company_id),
|
||||
manufacturer_data_size,
|
||||
cb_data);
|
||||
}
|
||||
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! @param ad_data The advertising data and scan response data to parse.
|
||||
//! @param callbacks Callbacks for each of the types of data the client wants
|
||||
//! to receive parse callbacks for. You can leave a callback NULL if you are not
|
||||
//! interested in receiving callbacks for that type of data.
|
||||
//! @param cb_data Pointer to client data that is passed into the callback.
|
||||
static void ble_ad_parse_ad_data(const BLEAdData *ad_data,
|
||||
const BLEAdParseCallbacks *callbacks,
|
||||
void *cb_data) {
|
||||
const uint8_t *cursor = ad_data->data;
|
||||
const uint8_t *end = cursor + ad_data->ad_data_length +
|
||||
ad_data->scan_resp_data_length;
|
||||
while (cursor < end) {
|
||||
|
||||
const BLEAdElement *elem = (const BLEAdElement *) cursor;
|
||||
if (elem->header.length == 0) {
|
||||
// We've hit a padding zero. We should be done, or this packet is corrupt.
|
||||
return;
|
||||
}
|
||||
|
||||
if (cursor + elem->header.length + 1 /* +1 length byte */ > end) {
|
||||
return; // corrupted
|
||||
}
|
||||
|
||||
bool (*parse_func)(const BLEAdElement *,
|
||||
const BLEAdParseCallbacks *, void *) = NULL;
|
||||
|
||||
switch (elem->header.type) {
|
||||
case BLEAdTypeService16BitUUIDPartial ... BLEAdTypeService128BitUUIDComplete:
|
||||
if (callbacks->services_cb) {
|
||||
parse_func = parse_services_list;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLEAdTypeLocalNameShortened ... BLEAdTypeLocalNameComplete:
|
||||
if (callbacks->local_name_cb) {
|
||||
parse_func = parse_local_name;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLEAdTypeTxPowerLevel:
|
||||
if (callbacks->tx_power_level_cb) {
|
||||
parse_func = parse_power_level;
|
||||
}
|
||||
break;
|
||||
|
||||
case BLEAdTypeManufacturerSpecific:
|
||||
if (callbacks->manufacturer_cb) {
|
||||
parse_func = parse_manufact_spec;
|
||||
}
|
||||
break;
|
||||
|
||||
default: // parse_func == NULL
|
||||
break;
|
||||
|
||||
} // switch()
|
||||
|
||||
if (parse_func) {
|
||||
if (!parse_func(elem, callbacks, cb_data)) {
|
||||
// The callback indicated we should not continue parsing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The Length byte itself is not counted, so +1:
|
||||
cursor += elem->header.length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_includes_service() wrapper and helper function:
|
||||
|
||||
struct IncludesServiceCtx {
|
||||
const Uuid *service_uuid;
|
||||
bool included;
|
||||
};
|
||||
|
||||
static bool includes_service_parse_cb(const Uuid uuids[], uint8_t count,
|
||||
void *cb_data) {
|
||||
struct IncludesServiceCtx *ctx = (struct IncludesServiceCtx *) cb_data;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (uuid_equal(ctx->service_uuid, &uuids[i])) {
|
||||
// Found!
|
||||
ctx->included = true;
|
||||
return false; // stop parsing
|
||||
}
|
||||
}
|
||||
return true; // continue parsing
|
||||
}
|
||||
|
||||
bool ble_ad_includes_service(const BLEAdData *ad, const Uuid *service_uuid) {
|
||||
struct IncludesServiceCtx ctx = {
|
||||
.service_uuid = service_uuid,
|
||||
.included = false,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = (const BLEAdParseCallbacks) {
|
||||
.services_cb = includes_service_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.included;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_copy_service_uuids() wrapper and helper function:
|
||||
|
||||
struct CopyServiceUUIDsCtx {
|
||||
Uuid *uuids_out;
|
||||
const uint8_t max;
|
||||
uint8_t copied;
|
||||
uint8_t total;
|
||||
};
|
||||
|
||||
static bool copy_services_parse_cb(const Uuid uuids[], uint8_t count,
|
||||
void *cb_data) {
|
||||
struct CopyServiceUUIDsCtx *ctx = (struct CopyServiceUUIDsCtx *)cb_data;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (ctx->copied < ctx->max) {
|
||||
// Still space left, so copy:
|
||||
const Uuid *uuid = &uuids[i];
|
||||
memcpy(&ctx->uuids_out[ctx->copied++], uuid, sizeof(Uuid));
|
||||
}
|
||||
++ctx->total;
|
||||
}
|
||||
return false; // stop parsing, only one Services UUID element allowed by spec
|
||||
}
|
||||
|
||||
uint8_t ble_ad_copy_service_uuids(const BLEAdData *ad,
|
||||
Uuid *uuids_out,
|
||||
uint8_t num_uuids) {
|
||||
struct CopyServiceUUIDsCtx ctx = {
|
||||
.uuids_out = uuids_out,
|
||||
.max = num_uuids,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.services_cb = copy_services_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.total;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_get_tx_power_level() wrapper and helper function:
|
||||
|
||||
struct TxPowerLevelCtx {
|
||||
int8_t *tx_power_level_out;
|
||||
bool included;
|
||||
};
|
||||
|
||||
static bool tx_power_level_cb(int8_t tx_power_level,
|
||||
void *cb_data) {
|
||||
struct TxPowerLevelCtx *ctx = (struct TxPowerLevelCtx *)cb_data;
|
||||
*ctx->tx_power_level_out = tx_power_level;
|
||||
ctx->included = true;
|
||||
return false; // stop parsing
|
||||
}
|
||||
|
||||
bool ble_ad_get_tx_power_level(const BLEAdData *ad,
|
||||
int8_t *tx_power_level_out) {
|
||||
struct TxPowerLevelCtx ctx = {
|
||||
.tx_power_level_out = tx_power_level_out,
|
||||
.included = false,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.tx_power_level_cb = tx_power_level_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.included;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_copy_local_name() wrapper and helper function:
|
||||
|
||||
struct LocalNameCtx {
|
||||
char *buffer;
|
||||
const uint8_t size;
|
||||
size_t copied_size;
|
||||
};
|
||||
|
||||
static bool copy_local_name_parse_cb(const uint8_t *local_name_bytes,
|
||||
uint8_t length, void *cb_data) {
|
||||
struct LocalNameCtx *ctx = (struct LocalNameCtx *)cb_data;
|
||||
const uint8_t copied_size = MIN(ctx->size, length + 1 /* zero terminator */);
|
||||
|
||||
memcpy(ctx->buffer, local_name_bytes, copied_size - 1);
|
||||
ctx->buffer[copied_size - 1] = 0; // zero terminator
|
||||
ctx->copied_size = copied_size;
|
||||
|
||||
return false; // stop parsing
|
||||
}
|
||||
|
||||
size_t ble_ad_copy_local_name(const BLEAdData *ad, char *buffer, size_t size) {
|
||||
struct LocalNameCtx ctx = {
|
||||
.buffer = buffer,
|
||||
.size = MIN(size, 0xff),
|
||||
.copied_size = 0,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.local_name_cb = copy_local_name_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
return ctx.copied_size;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_get_raw_data_size() wrapper and helper function:
|
||||
|
||||
size_t ble_ad_get_raw_data_size(const BLEAdData *ad) {
|
||||
return ad->ad_data_length + ad->scan_resp_data_length;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_copy_raw_data() wrapper and helper function:
|
||||
|
||||
size_t ble_ad_copy_raw_data(const BLEAdData *ad, uint8_t *buffer, size_t size) {
|
||||
const size_t size_to_copy = ble_ad_get_raw_data_size(ad);
|
||||
if (size < size_to_copy) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(buffer, ad->data, size_to_copy);
|
||||
return size_to_copy;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! ble_ad_get_manufacturer_specific_data() wrapper and helper function:
|
||||
|
||||
struct ManufacturerSpecificCtx {
|
||||
uint16_t company_id;
|
||||
uint8_t *buffer;
|
||||
const uint8_t size;
|
||||
size_t copied_size;
|
||||
};
|
||||
|
||||
static bool copy_manufacturer_specific_parse_cb(uint16_t company_id,
|
||||
const uint8_t *data,
|
||||
uint8_t length,
|
||||
void *cb_data) {
|
||||
|
||||
struct ManufacturerSpecificCtx *ctx =
|
||||
(struct ManufacturerSpecificCtx *) cb_data;
|
||||
const uint8_t copied_size = MIN(ctx->size, length);
|
||||
|
||||
memcpy(ctx->buffer, data, copied_size);
|
||||
ctx->copied_size = copied_size;
|
||||
ctx->company_id = company_id;
|
||||
|
||||
return false; // stop parsing
|
||||
}
|
||||
|
||||
size_t ble_ad_copy_manufacturer_specific_data(const BLEAdData *ad,
|
||||
uint16_t *company_id,
|
||||
uint8_t *buffer, size_t size) {
|
||||
struct ManufacturerSpecificCtx ctx = {
|
||||
.size = size,
|
||||
.buffer = buffer,
|
||||
};
|
||||
const BLEAdParseCallbacks callbacks = {
|
||||
.manufacturer_cb = copy_manufacturer_specific_parse_cb,
|
||||
};
|
||||
ble_ad_parse_ad_data(ad, &callbacks, &ctx);
|
||||
if (company_id) {
|
||||
*company_id = ctx.company_id;
|
||||
}
|
||||
return ctx.copied_size;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Creating BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
//! Magic high bit used as scan_resp_data_length to indicate that the ad_data
|
||||
//! has been finalized and the next write should be counted towards the scan
|
||||
//! response payload. The maximum scan_resp_data_length is 31 bytes, so this
|
||||
//! value lies outside of the valid range. This is basically a memory savings
|
||||
//! optimization, saving another "finalized" bool.
|
||||
#define BLE_AD_DATA_FINALIZED ((uint8_t) 0x80)
|
||||
|
||||
bool prv_ad_is_finalized(const BLEAdData *ad_data) {
|
||||
// Scan response data has already been added / started
|
||||
return (ad_data->scan_resp_data_length != 0);
|
||||
}
|
||||
|
||||
void ble_ad_start_scan_response(BLEAdData *ad_data) {
|
||||
if (prv_ad_is_finalized(ad_data)) {
|
||||
// Already finalized
|
||||
return;
|
||||
}
|
||||
ad_data->scan_resp_data_length = BLE_AD_DATA_FINALIZED;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Helper to calculate whether a number of bytes will still fit when appended.
|
||||
//! @param length Pointer to the length of the part for which to try to fit in
|
||||
//! size_to_write number of bytes.
|
||||
//! @param size_to_write The number of bytes to that need to be appended.
|
||||
//! @return Pointer to length if it could still appends size_to_write bytes or
|
||||
//! NULL if not.
|
||||
static uint8_t *prv_length_ptr_if_fits_or_null(uint8_t *length,
|
||||
size_t size_to_write) {
|
||||
// Unset finalized bit:
|
||||
const uint8_t used = *length & (~BLE_AD_DATA_FINALIZED);
|
||||
const uint8_t left = GAP_LE_AD_REPORT_DATA_MAX_LENGTH - used;
|
||||
// Return pointer to the pointer if size_to_write will fit, or NULL otherwise:
|
||||
return (left >= size_to_write) ? length : NULL;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! @return Pointer to the length that is incremented when writing size_to_write
|
||||
//! number of bytes, or NULL if there is not enough space left.
|
||||
static uint8_t* prv_length_to_increase(BLEAdData *ad_data,
|
||||
size_t size_to_write) {
|
||||
if (ad_data->scan_resp_data_length) {
|
||||
// The scan response part is already being populated:
|
||||
return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
|
||||
size_to_write);
|
||||
} else {
|
||||
// The advertisement is still being populated:
|
||||
uint8_t *length = prv_length_ptr_if_fits_or_null(&ad_data->ad_data_length,
|
||||
size_to_write);
|
||||
if (length) {
|
||||
// Hurray, the size_to_write fits in the advertisement part:
|
||||
return length;
|
||||
}
|
||||
// Last resort, try fitting into scan response part:
|
||||
return prv_length_ptr_if_fits_or_null(&ad_data->scan_resp_data_length,
|
||||
size_to_write);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool prv_write_element_to_ad_data(BLEAdData *ad_data,
|
||||
const BLEAdElement *element) {
|
||||
if (!ad_data || !element) {
|
||||
return false;
|
||||
}
|
||||
const size_t size_to_write = element->header.length + 1 /* Length Byte */;
|
||||
uint8_t* length = prv_length_to_increase(ad_data, size_to_write);
|
||||
if (!length) {
|
||||
// Not enough space...
|
||||
return false;
|
||||
}
|
||||
|
||||
// Undo the magic number trick:
|
||||
if (*length == BLE_AD_DATA_FINALIZED) {
|
||||
*length = 0;
|
||||
}
|
||||
|
||||
// Append the element to the end:
|
||||
uint8_t * const end = ad_data->data +
|
||||
ad_data->ad_data_length +
|
||||
ad_data->scan_resp_data_length;
|
||||
memcpy(end, (const uint8_t *) element, size_to_write);
|
||||
|
||||
// Length book-keeping:
|
||||
*length += size_to_write;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
BLEAdData * ble_ad_create(void) {
|
||||
const size_t max_ad_data_size = sizeof(BLEAdData) +
|
||||
(GAP_LE_AD_REPORT_DATA_MAX_LENGTH * 2);
|
||||
BLEAdData *ad_data = applib_malloc(max_ad_data_size);
|
||||
if (ad_data) {
|
||||
memset(ad_data, 0, sizeof(BLEAdData));
|
||||
}
|
||||
return ad_data;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
void ble_ad_destroy(BLEAdData *ad) {
|
||||
applib_free(ad);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! The smallest UUID width, by reducing the width when a UUID is based on the
|
||||
//! Bluetooth base UUID. @see bt_uuid_expand_16bit @see bt_uuid_expand_32bit
|
||||
static uint8_t prv_smallest_bt_uuid_width_in_bytes(const Uuid *uuid) {
|
||||
const Uuid bt_uuid_base = bt_uuid_expand_16bit(0);
|
||||
|
||||
// The bytes after the first 4 contain the Bluetooth base.
|
||||
// Check if the uuid is based off of the Bluetooth base UUID:
|
||||
const bool is_bt_uuid_based = (memcmp(&bt_uuid_base.byte4, &uuid->byte4,
|
||||
sizeof(Uuid) - offsetof(Uuid, byte4)) == 0);
|
||||
if (!is_bt_uuid_based) {
|
||||
// Not based on the Bluetooth base UUID, so use 128-bits:
|
||||
return sizeof(Uuid);
|
||||
}
|
||||
if (uuid->byte0 || uuid->byte1) {
|
||||
// If byte0 and byte1 not zero: 32-bit UUID, Bluetooth base UUID based:
|
||||
return sizeof(uint32_t);
|
||||
}
|
||||
// If byte0 and byte1 are zero: 16-bit UUID, Bluetooth base UUID based:
|
||||
return sizeof(uint16_t);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Finds the largest common UUID width. For UUIDs that are based on the
|
||||
//! Bluetooth base UUID, a reduced width will be taken of either 16-bits or
|
||||
//! 32-bits.
|
||||
static uint8_t prv_largest_common_bt_uuid_width(const Uuid uuids[],
|
||||
uint8_t num_uuids) {
|
||||
uint8_t max_width_bytes = sizeof(uint16_t);
|
||||
for (unsigned int i = 0; i < num_uuids; ++i) {
|
||||
const Uuid *uuid = &uuids[i];
|
||||
const uint8_t width_bytes = prv_smallest_bt_uuid_width_in_bytes(uuid);
|
||||
max_width_bytes = MAX(width_bytes, max_width_bytes);
|
||||
}
|
||||
return max_width_bytes;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Helper to reduces a 128-bit UUID to 16-bits. Note: this function does not
|
||||
//! check whether the original UUID is based on the Bluetooth base.
|
||||
static uint16_t prv_convert_to_16bit_uuid(const Uuid *uuid) {
|
||||
uint16_t uuid_16bits = 0;
|
||||
// Use bytes 2-3 of the Uuid:
|
||||
for (int i = 2; i < 4; ++i) {
|
||||
uuid_16bits <<= 8;
|
||||
uuid_16bits += ((const uint8_t *) uuid)[i];
|
||||
}
|
||||
return uuid_16bits;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Helper to reduces a 128-bit UUID to 32-bits. Note: this function does not
|
||||
//! check whether the original UUID is based on the Bluetooth base.
|
||||
static uint32_t prv_convert_to_32bit_uuid(const Uuid *uuid) {
|
||||
uint32_t uuid_32bits = 0;
|
||||
// Use bytes 0-3 of the Uuid:
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
uuid_32bits <<= 8;
|
||||
uuid_32bits += ((const uint8_t *) uuid)[i];
|
||||
}
|
||||
return uuid_32bits;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_service_uuids(BLEAdData *ad,
|
||||
const Uuid uuids[], uint8_t num_uuids) {
|
||||
struct __attribute__((__packed__)) BLEAdElementService {
|
||||
BLEAdElementHeader header;
|
||||
union {
|
||||
Uuid uuid_128[0];
|
||||
uint32_t uuid_32[0];
|
||||
uint16_t uuid_16[0];
|
||||
};
|
||||
};
|
||||
|
||||
const uint8_t max_width_bytes = prv_largest_common_bt_uuid_width(uuids,
|
||||
num_uuids);
|
||||
// Allocate buffer:
|
||||
const size_t buffer_size = sizeof(struct BLEAdElementService) +
|
||||
(max_width_bytes * num_uuids);
|
||||
uint8_t element_buffer[buffer_size];
|
||||
struct BLEAdElementService *element = (struct BLEAdElementService *) element_buffer;
|
||||
|
||||
// Set header fields (assume Complete):
|
||||
switch (max_width_bytes) {
|
||||
case 16: element->header.type = BLEAdTypeService128BitUUIDComplete; break;
|
||||
case 4: element->header.type = BLEAdTypeService32BitUUIDComplete; break;
|
||||
case 2: element->header.type = BLEAdTypeService16BitUUIDComplete; break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
element->header.length = buffer_size - 1 /* -1 Length byte */;
|
||||
|
||||
// Copy UUIDs:
|
||||
for (unsigned int i = 0; i < num_uuids; ++i) {
|
||||
switch (max_width_bytes) {
|
||||
case 16: element->uuid_128[i] = uuids[i]; break;
|
||||
case 4: element->uuid_32[i] = prv_convert_to_32bit_uuid(&uuids[i]); break;
|
||||
case 2: element->uuid_16[i] = prv_convert_to_16bit_uuid(&uuids[i]); break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_local_name(BLEAdData *ad,
|
||||
const char *local_name) {
|
||||
if (!local_name) {
|
||||
return false;
|
||||
}
|
||||
const size_t length = strlen(local_name);
|
||||
uint8_t element_buffer[sizeof(BLEAdElement) + length];
|
||||
BLEAdElement *element = (BLEAdElement *) &element_buffer;
|
||||
element->header.length = length + 1 /* +1 Type byte */;
|
||||
element->header.type = BLEAdTypeLocalNameComplete; /* assume Complete */
|
||||
// Note: *not* zero terminated by design
|
||||
memcpy(element->data, local_name, length);
|
||||
return prv_write_element_to_ad_data(ad, element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_tx_power_level(BLEAdData *ad) {
|
||||
uint8_t element_buffer[sizeof(BLEAdElement) + sizeof(int8_t)];
|
||||
BLEAdElement *element = (BLEAdElement *) element_buffer;
|
||||
element->header.length = sizeof(int8_t) + 1 /* +1 Type byte */;
|
||||
element->header.type = BLEAdTypeTxPowerLevel;
|
||||
*((int8_t *) element->data) = sys_ble_get_advertising_tx_power();
|
||||
|
||||
return prv_write_element_to_ad_data(ad, element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_manufacturer_specific_data(BLEAdData *ad, uint16_t company_id,
|
||||
const uint8_t *data, size_t size) {
|
||||
struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
|
||||
BLEAdElementHeader header;
|
||||
uint16_t company_id;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
uint8_t element_buffer[sizeof(struct BLEAdElementManufacturerSpecific) + size];
|
||||
struct BLEAdElementManufacturerSpecific *element =
|
||||
(struct BLEAdElementManufacturerSpecific *) element_buffer;
|
||||
element->header.length = sizeof(struct BLEAdElementManufacturerSpecific)
|
||||
- 1 /* -1 Length byte */ + size;
|
||||
element->header.type = BLEAdTypeManufacturerSpecific;
|
||||
element->company_id = ltohs(company_id);
|
||||
memcpy(element->data, data, size);
|
||||
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) element);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool ble_ad_set_flags(BLEAdData *ad, uint8_t flags) {
|
||||
struct __attribute__((__packed__)) BLEAdElementManufacturerSpecific {
|
||||
BLEAdElementHeader header;
|
||||
uint8_t flags;
|
||||
} element = {
|
||||
.header = {
|
||||
.length = sizeof(struct BLEAdElementManufacturerSpecific)
|
||||
- 1 /* -1 Length byte */,
|
||||
.type = BLEAdTypeFlags,
|
||||
},
|
||||
.flags = flags,
|
||||
};
|
||||
return prv_write_element_to_ad_data(ad, (const BLEAdElement *) &element);
|
||||
}
|
206
src/fw/applib/bluetooth/ble_ad_parse.h
Normal file
206
src/fw/applib/bluetooth/ble_ad_parse.h
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! @file ble_ad_parse.h
|
||||
//! API to serialize and deserialize advertisment and scan response payloads.
|
||||
//!
|
||||
//! Inbound payloads, as received using the ble_scan.h public API, can be
|
||||
//! consumed/deserialized using the functions below.
|
||||
//!
|
||||
//! Outbound payloads can be created/serialized and then advertised using the
|
||||
//! gap_le_advert.h functions. At the moment, there is no public API.
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Consuming BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
//! Searching the advertisement data to check whether a given service UUID is
|
||||
//! included.
|
||||
//! @param ad The advertisement data
|
||||
//! @param service_uuid The UUID of the service to look for
|
||||
//! @return true if the service UUID was found, false if not
|
||||
bool ble_ad_includes_service(const BLEAdData *ad, const Uuid *service_uuid);
|
||||
|
||||
//! If present, copies the Service UUIDs from the advertisement data.
|
||||
//! @param ad The advertisement data
|
||||
//! @param[out] uuids_out An array of Uuid`s into which the found Service UUIDs
|
||||
//! will be copied.
|
||||
//! @param num_uuids The size of the uuids_out array.
|
||||
//! @return The total number of found Service UUIDs. This might be a larger
|
||||
//! number than num_uuids, if the passed array was not large enough to hold all
|
||||
//! the UUIDs.
|
||||
//! @note All UUIDs from advertisement data will be converted to their 128-bit
|
||||
//! equivalents using the Bluetooth Base UUID using bt_uuid_expand_16bit or
|
||||
//! bt_uuid_expand_32bit.
|
||||
//! @see ble_ad_get_number_of_service_uuids
|
||||
uint8_t ble_ad_copy_service_uuids(const BLEAdData *ad,
|
||||
Uuid *uuids_out,
|
||||
uint8_t num_uuids);
|
||||
|
||||
//! If present, returns the number of Service UUIDs the advertisement data
|
||||
//! contains.
|
||||
//! @param ad The advertisement data
|
||||
//! @return If Service UUIDs data is present, the number of UUIDs is contains,
|
||||
//! or zero otherwise.
|
||||
uint8_t ble_ad_get_number_of_service_uuids(const BLEAdData *ad);
|
||||
|
||||
//! If present, gets the TX Power Level from the advertisement data.
|
||||
//! @param ad The advertisement data
|
||||
//! @param[out] tx_power_level_out Will contain the TX Power Level if the return
|
||||
//! value is true.
|
||||
//! @return true if the TX Power Level was found and assigned.
|
||||
bool ble_ad_get_tx_power_level(const BLEAdData *ad, int8_t *tx_power_level_out);
|
||||
|
||||
//! If present, copies the Local Name from the advertisement data.
|
||||
//! If the Local Name is bigger than the size of the buffer, only the part that
|
||||
//! fits will be copied. For convenience, the copied c-string will always be
|
||||
//! zero terminated for you.
|
||||
//! @param ad The advertisement data
|
||||
//! @param buffer The buffer into which to copy the Local Name, if found.
|
||||
//! @param size The size of the buffer
|
||||
//! @return The size of the Local Name in bytes, *including* zero terminator.
|
||||
//! Note that this might be more than the size of the provided buffer.
|
||||
//! If the Local Name was not found, the return value will be zero.
|
||||
size_t ble_ad_copy_local_name(const BLEAdData *ad,
|
||||
char *buffer, size_t size);
|
||||
|
||||
//! If the Local Name is present in the advertisment data, returns the number
|
||||
//! of bytes a C-string needs to be to hold the full name.
|
||||
//! @param ad The advertisement data
|
||||
//! @return The size of the Local Name in bytes, *including* zero terminator.
|
||||
//! If the Local Name is not present, zero is returned.
|
||||
size_t ble_ad_get_local_name_buffer_size(const BLEAdData *ad);
|
||||
|
||||
//! If present, copies the Manufacturer Specific data from the advertisement
|
||||
//! data. If the provided buffer is smaller than the size of the data, only
|
||||
//! the data up that fits the buffer will be copied.
|
||||
//! @param ad The advertisement data
|
||||
//! @param[out] company_id_out Out: The Company Identifier Code, see
|
||||
//! https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
|
||||
//! This will only get written, if there was Manufacturer Specific data in the
|
||||
//! advertisement. In case you are not interested in getting the company ID,
|
||||
//! NULL can be passed in.
|
||||
//! @param buffer The buffer into which to copy the Manufacturer Specific Data,
|
||||
//! if found.
|
||||
//! @param size The size of the buffer
|
||||
//! @return The size of the Manufacturer Specific data in bytes. Note that this
|
||||
//! can be larger than the size of the provided buffer. If the Manufacturer
|
||||
//! Specific data was not found, the return value will be zero.
|
||||
//! @see ble_ad_get_manufacturer_specific_data_size
|
||||
size_t ble_ad_copy_manufacturer_specific_data(const BLEAdData *ad,
|
||||
uint16_t *company_id_out,
|
||||
uint8_t *buffer, size_t size);
|
||||
|
||||
//! Gets the size in bytes of Manufacturer Specific data in the advertisment.
|
||||
//! @param ad The advertisement data
|
||||
//! @return The size of the data, in bytes. If the Manufacturer Specific data is
|
||||
//! not present, zero is returned.
|
||||
size_t ble_ad_get_manufacturer_specific_data_size(const BLEAdData *ad);
|
||||
|
||||
//! Gets the size in bytes of the raw advertisement and scan response data.
|
||||
//! @param ad The advertisement data
|
||||
//! @return The size of the raw advertisement and scan response data in bytes.
|
||||
size_t ble_ad_get_raw_data_size(const BLEAdData *ad);
|
||||
|
||||
//! Copies the raw bytes of advertising and scan response data into a buffer.
|
||||
//! If there was scan response data, it will be concatenated directly after the
|
||||
//! advertising data.
|
||||
//! @param ad The advertisement data
|
||||
//! @param buffer The buffer into which to copy the raw bytes
|
||||
//! @param size The size of the buffer
|
||||
//! @return The number of bytes copied.
|
||||
size_t ble_ad_copy_raw_data(const BLEAdData *ad, uint8_t *buffer, size_t size);
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Creating BLEAdData:
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
//! Creates a blank, mutable advertisement and scan response payload.
|
||||
//! It can contain up to 31 bytes of advertisement data and up to 31 bytes of
|
||||
//! scan response data. The underlying storage for this is automatically
|
||||
//! allocated.
|
||||
//! @note When adding elements to the BLEAdData, using the ble_ad_set_...
|
||||
//! functions, elements will be added to the advertisement data part first.
|
||||
//! If the element to add does not fit, the scan response is automatically
|
||||
//! used. Added elements cannot span across the two parts.
|
||||
//! @return The blank payload object.
|
||||
//! @note BLEAdData created with this function must be destroyed at some point
|
||||
//! in time using ble_ad_destroy()
|
||||
//! @note Use the ble_ad_set... functions to write data into the object. The
|
||||
//! data written to it will occupy the advertisement payload until there is not
|
||||
//! enough space left, in which case all following data is written into the scan
|
||||
//! response. @see ble_ad_start_scan_response()
|
||||
BLEAdData* ble_ad_create(void);
|
||||
|
||||
//! Destroys an advertisement payload that was created earlier with
|
||||
//! ble_ad_create().
|
||||
void ble_ad_destroy(BLEAdData *ad);
|
||||
|
||||
//! Marks the start of the scan response and finalizes the advertisement
|
||||
//! payload. This forces successive writes to be written to the scan response,
|
||||
//! even though it would have fit into the advertisement payload.
|
||||
void ble_ad_start_scan_response(BLEAdData *ad_data);
|
||||
|
||||
//! Writes the Service UUID list to the advertisement or scan response payload.
|
||||
//! The list is assumed to be the complete list of Service UUIDs.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param uuids Array of UUIDs to add to the list. If the UUIDs are all derived
|
||||
//! from the Bluetooth SIG base UUID, this function will automatically use
|
||||
//! a smaller Service UUID size if possible.
|
||||
//! @see bt_uuid_expand_16bit
|
||||
//! @see bt_uuid_expand_32bit
|
||||
//! @param num_uuids Number of UUIDs in the uuids array.
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_service_uuids(BLEAdData *ad,
|
||||
const Uuid uuids[], uint8_t num_uuids);
|
||||
|
||||
//! Writes the Local Name to the advertisement or scan response payload.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param local_name Zero terminated, UTF-8 string with the Local Name. The
|
||||
//! name is assumed to be complete and not abbreviated.
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_local_name(BLEAdData *ad,
|
||||
const char *local_name);
|
||||
|
||||
//! Writes the TX Power Level to advertisement or scan response payload.
|
||||
//! The actual transmission power level value is set automatically, based on the
|
||||
//! value as used by the Bluetooth hardware.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_tx_power_level(BLEAdData *ad);
|
||||
|
||||
//! Writes Manufacturer Specific Data to advertisement or scan response payload.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param company_id The Company Identifier Code, see
|
||||
//! https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
|
||||
//! @param data The data
|
||||
//! @param size The size of data in bytes
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_manufacturer_specific_data(BLEAdData *ad,
|
||||
uint16_t company_id,
|
||||
const uint8_t *data, size_t size);
|
||||
|
||||
//! @internal -- Do not export
|
||||
//! Writes the Flags AD Type to the advertisement or scan response payload.
|
||||
//! @param ad The advertisement payload as created earlier by ble_ad_create()
|
||||
//! @param flags The flags to write. See Core_v4.0.pdf Vol 3, Appendix C, 18.1.
|
||||
//! @return true if the data was successfully written or false if not.
|
||||
bool ble_ad_set_flags(BLEAdData *ad, uint8_t flags);
|
66
src/fw/applib/bluetooth/ble_app_support.c
Normal file
66
src/fw/applib/bluetooth/ble_app_support.c
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_app_support.h"
|
||||
|
||||
#include "comm/ble/gap_le_scan.h"
|
||||
#include "comm/ble/gap_le_connect.h"
|
||||
#include "comm/ble/gatt_client_operations.h"
|
||||
#include "comm/ble/gatt_client_subscriptions.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
|
||||
//! @see ble_scan.c
|
||||
extern void ble_scan_handle_event(PebbleEvent *e, void *context);
|
||||
|
||||
//! @see ble_central.c
|
||||
extern void ble_central_handle_event(PebbleEvent *e, void *context);
|
||||
|
||||
//! @see ble_client.c
|
||||
extern void ble_client_handle_event(PebbleEvent *e, void *context);
|
||||
|
||||
void ble_init_app_state(void) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
*ble_app_state = (const BLEAppState) {
|
||||
// ble_scan_...:
|
||||
.scan_service_info = (const EventServiceInfo) {
|
||||
.type = PEBBLE_BLE_SCAN_EVENT,
|
||||
.handler = ble_scan_handle_event,
|
||||
},
|
||||
|
||||
// ble_central_...:
|
||||
.connection_service_info = (const EventServiceInfo) {
|
||||
.type = PEBBLE_BLE_CONNECTION_EVENT,
|
||||
.handler = ble_central_handle_event,
|
||||
},
|
||||
|
||||
// ble_client_...:
|
||||
.gatt_client_service_info = (const EventServiceInfo) {
|
||||
.type = PEBBLE_BLE_GATT_CLIENT_EVENT,
|
||||
.handler = ble_client_handle_event,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void ble_app_cleanup(void) {
|
||||
// Gets run on the KernelMain task.
|
||||
// All kernel / shared resources for BLE that were allocated on behalf of the
|
||||
// app must be freed / released here:
|
||||
gap_le_stop_scan();
|
||||
gap_le_connect_cancel_all(GAPLEClientApp);
|
||||
gatt_client_subscriptions_cleanup_by_client(GAPLEClientApp);
|
||||
gatt_client_op_cleanup(GAPLEClientApp);
|
||||
}
|
54
src/fw/applib/bluetooth/ble_app_support.h
Normal file
54
src/fw/applib/bluetooth/ble_app_support.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ble_scan.h"
|
||||
#include "ble_central.h"
|
||||
#include "ble_client.h"
|
||||
|
||||
#include "applib/event_service_client.h"
|
||||
|
||||
typedef struct {
|
||||
// Scanning
|
||||
EventServiceInfo scan_service_info;
|
||||
BLEScanHandler scan_handler;
|
||||
|
||||
// Connecting
|
||||
EventServiceInfo connection_service_info;
|
||||
BLEConnectionHandler connection_handler;
|
||||
|
||||
// GATT Client
|
||||
EventServiceInfo gatt_client_service_info;
|
||||
|
||||
BLEClientServiceChangeHandler gatt_service_change_handler;
|
||||
|
||||
BLEClientReadHandler gatt_characteristic_read_handler;
|
||||
BLEClientWriteHandler gatt_characteristic_write_handler;
|
||||
BLEClientSubscribeHandler gatt_characteristic_subscribe_handler;
|
||||
|
||||
BLEClientReadDescriptorHandler gatt_descriptor_read_handler;
|
||||
BLEClientWriteDescriptorHandler gatt_descriptor_write_handler;
|
||||
|
||||
uint8_t gatt_client_num_handlers;
|
||||
} BLEAppState;
|
||||
|
||||
//! Initializes the static BLE state for the currently running app.
|
||||
void ble_init_app_state(void);
|
||||
|
||||
//! Must be called by the kernel, after an app is killed to stop any ongoing
|
||||
//! BLE activity that was running on behalf of the app.
|
||||
void ble_app_cleanup(void);
|
57
src/fw/applib/bluetooth/ble_central.c
Normal file
57
src/fw/applib/bluetooth/ble_central.c
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_central.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
|
||||
#include "comm/ble/gap_le_connect.h"
|
||||
|
||||
static BTErrno prv_bt_errno_for_event(const PebbleBLEConnectionEvent *e) {
|
||||
if (e->connected) {
|
||||
return BTErrnoConnected;
|
||||
}
|
||||
|
||||
// FIXME: PBL-35506 We need to re-evaluate what error code to actually use here
|
||||
return e->hci_reason;
|
||||
}
|
||||
|
||||
void ble_central_handle_event(PebbleEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (!ble_app_state->connection_handler) {
|
||||
return;
|
||||
}
|
||||
const PebbleBLEConnectionEvent *conn_event = &e->bluetooth.le.connection;
|
||||
const BTDeviceInternal device = PebbleEventToBTDeviceInternal(conn_event);
|
||||
ble_app_state->connection_handler(device.opaque,
|
||||
prv_bt_errno_for_event(conn_event));
|
||||
}
|
||||
|
||||
BTErrno ble_central_set_connection_handler(BLEConnectionHandler handler) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const bool is_subscribed = (ble_app_state->connection_handler != NULL);
|
||||
ble_app_state->connection_handler = handler;
|
||||
if (handler) {
|
||||
if (!is_subscribed) {
|
||||
event_service_client_subscribe(&ble_app_state->connection_service_info);
|
||||
}
|
||||
} else {
|
||||
if (is_subscribed) {
|
||||
event_service_client_unsubscribe(&ble_app_state->connection_service_info);
|
||||
}
|
||||
}
|
||||
return BTErrnoOK;
|
||||
}
|
86
src/fw/applib/bluetooth/ble_central.h
Normal file
86
src/fw/applib/bluetooth/ble_central.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Callback that is called for each connection and disconnection event.
|
||||
//! @param device The device that got (dis)connected
|
||||
//! @param connection_status BTErrnoConnected if connected, otherwise the
|
||||
//! reason for the disconnection: BTErrnoConnectionTimeout,
|
||||
//! BTErrnoRemotelyTerminated, BTErrnoLocallyTerminatedBySystem or
|
||||
//! BTErrnoLocallyTerminatedByApp.
|
||||
//! @note See additional notes with ble_central_set_connection_handler()
|
||||
typedef void (*BLEConnectionHandler)(BTDevice device,
|
||||
BTErrno connection_status);
|
||||
|
||||
//! Registers the connection event handler of the application.
|
||||
//! This event handler will be called when connections and disconnection occur,
|
||||
//! for devices for which ble_central_connect() has been called by the
|
||||
//! application.
|
||||
//! Only for successful connections and complete disconnections will the event
|
||||
//! handler be called. Transient issues that might happen during connection
|
||||
//! establishment will be not be reported to the application. Instead, the
|
||||
//! system will attempt to initiate a connection to the device again.
|
||||
//! If this is called again, the previous handler will be unregistered.
|
||||
//! @param handler The connection event handler of the application
|
||||
//! @return Always returns BTErrnoOK.
|
||||
BTErrno ble_central_set_connection_handler(BLEConnectionHandler handler);
|
||||
|
||||
//! Attempts to initiate a connection from the application to another device.
|
||||
//! In case there is no Bluetooth connection to the device yet, this function
|
||||
//! configures the Bluetooth subsystem to scan for the specified
|
||||
//! device and instructs it to connect to it as soon as a connectable
|
||||
//! advertisement has been received. The application will need to register a
|
||||
//! connection event handler prior to calling ble_central_connect(), using
|
||||
//! ble_central_set_connection_handler().
|
||||
//! Outstanding (auto)connection attempts can be cancelled using
|
||||
//! ble_central_cancel_connect().
|
||||
//! @note Connections are virtualized. This means that your application needs to
|
||||
//! call the ble_central_connect, even though the device might already have an
|
||||
//! actual Bluetooth connection already. This can be the case when connecting
|
||||
//! to the user's phone: it is likely the system has created a Bluetooth
|
||||
//! connection already, still the application has to connect internally in order
|
||||
//! to use the connection.
|
||||
//! @param device The device to connect to
|
||||
//! @param auto_reconnect Pass in true to automatically attempt to reconnect
|
||||
//! again if the connection is lost. The BLEConnectionHandler will be called for
|
||||
//! each time the device is (re)connected.
|
||||
//! Pass in false, if the system should connect once only.
|
||||
//! The BLEConnectionHandler will be called up to one time only.
|
||||
//! @param is_pairing_required If the application requires that the Bluetooth
|
||||
//! traffic is encrypted, is_pairing_required can be set to true to let the
|
||||
//! system transparently set up pairing, or reestablish encryption, if the
|
||||
//! device is already paired. Only after this security procedure is finished,
|
||||
//! the BLEConnectionHandler will be called. Note that not all devices support
|
||||
//! pairing and one of the BTErrnoPairing... errors can result from a failed
|
||||
//! pairing process. If the application does not require pairing, set to false.
|
||||
//! @note It is possible that encryption is still enabled, even if the
|
||||
//! application did not require this.
|
||||
//! @return BTErrnoOK if the intent to connect was processed successfully, or
|
||||
//! ... TODO
|
||||
BTErrno ble_central_connect(BTDevice device,
|
||||
bool auto_reconnect,
|
||||
bool is_pairing_required);
|
||||
|
||||
//! Attempts to cancel the connection, as initiated by ble_central_connect().
|
||||
//! The underlying Bluetooth connection might not be disconnected if the
|
||||
//! connection is still in use by the system. However, as far as the application
|
||||
//! is concerned, the device is disconnected and the connection handler will
|
||||
//! be called with BTErrnoLocallyTerminatedByApp.
|
||||
//! @return BTErrnoOK if the cancelling was successful, or ... TODO
|
||||
BTErrno ble_central_cancel_connect(BTDevice device);
|
49
src/fw/applib/bluetooth/ble_characteristic.c
Normal file
49
src/fw/applib/bluetooth/ble_characteristic.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_characteristic.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
bool ble_characteristic_is_readable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyRead);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_writable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyWrite);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_writable_without_response(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyWriteWithoutResponse);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_subscribable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
(BLEAttributePropertyNotify | BLEAttributePropertyIndicate));
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_notifiable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyNotify);
|
||||
}
|
||||
|
||||
bool ble_characteristic_is_indicatable(BLECharacteristic characteristic) {
|
||||
return (sys_ble_characteristic_get_properties(characteristic) &
|
||||
BLEAttributePropertyIndicate);
|
||||
}
|
99
src/fw/applib/bluetooth/ble_characteristic.h
Normal file
99
src/fw/applib/bluetooth/ble_characteristic.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Gets the UUID for a characteristic.
|
||||
//! @param characteristic The characteristic for which to get the UUID
|
||||
//! @return The UUID of the characteristic
|
||||
Uuid ble_characteristic_get_uuid(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the properties bitset for a characteristic.
|
||||
//! @param characteristic The characteristic for which to get the properties
|
||||
//! @return The properties bitset
|
||||
BLEAttributeProperty ble_characteristic_get_properties(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is readable or not.
|
||||
//! @param characteristic The characteristic for which to get its readability.
|
||||
//! @return true if the characteristic is readable, false if it is not.
|
||||
bool ble_characteristic_is_readable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is writable or not.
|
||||
//! @param characteristic The characteristic for which to get its write-ability.
|
||||
//! @return true if the characteristic is writable, false if it is not.
|
||||
bool ble_characteristic_is_writable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is writable without response or not.
|
||||
//! @param characteristic The characteristic for which to get its write-ability.
|
||||
//! @return true if the characteristic is writable without response, false if it is not.
|
||||
bool ble_characteristic_is_writable_without_response(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is subscribable or not.
|
||||
//! @param characteristic The characteristic for which to get its subscribability.
|
||||
//! @return true if the characteristic is subscribable, false if it is not.
|
||||
bool ble_characteristic_is_subscribable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is notifiable or not.
|
||||
//! @param characteristic The characteristic for which to get its notifiability.
|
||||
//! @return true if the characteristic is notifiable, false if it is not.
|
||||
bool ble_characteristic_is_notifiable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets whether the characteristic is indicatable or not.
|
||||
//! @param characteristic The characteristic for which to get its indicatability.
|
||||
//! @return true if the characteristic is indicatable, false if it is not.
|
||||
bool ble_characteristic_is_indicatable(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the service that the characteristic belongs to.
|
||||
//! @param characteristic The characteristic for which to find the service it
|
||||
//! belongs to.
|
||||
//! @return The service owning the characteristic
|
||||
BLEService ble_characteristic_get_service(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the device that the characteristic belongs to.
|
||||
//! @param characteristic The characteristic for which to find the device it
|
||||
//! belongs to.
|
||||
//! @return The device owning the characteristic.
|
||||
BTDevice ble_characteristic_get_device(BLECharacteristic characteristic);
|
||||
|
||||
//! Gets the descriptors associated with the characteristic.
|
||||
//! @param characteristic The characteristic for which to get the descriptors
|
||||
//! @param[out] descriptors_out An array of pointers to descriptors, into which
|
||||
//! the associated descriptors will be copied.
|
||||
//! @param num_descriptors The size of the descriptors_out array.
|
||||
//! @return The total number of descriptors for the service. This might be a
|
||||
//! larger number than num_descriptors will contain, if the passed array was
|
||||
//! not large enough to hold all the pointers.
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
uint8_t ble_characteristic_get_descriptors(BLECharacteristic characteristic,
|
||||
BLEDescriptor descriptors_out[],
|
||||
uint8_t num_descriptors);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs would be:
|
||||
|
||||
BLECharacteristic ble_characteristic_create(const Uuid *uuid,
|
||||
BLEAttributeProperty properties);
|
||||
|
||||
BLECharacteristic ble_characteristic_create_with_descriptors(const Uuid *uuid,
|
||||
BLEAttributeProperty properties,
|
||||
BLEDescriptor descriptors[],
|
||||
uint8_t num_descriptors);
|
305
src/fw/applib/bluetooth/ble_client.c
Normal file
305
src/fw/applib/bluetooth/ble_client.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#define FILE_LOG_COLOR LOG_COLOR_BLUE
|
||||
|
||||
#include "ble_client.h"
|
||||
|
||||
#include "ble_app_support.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// TODO:
|
||||
// - device name
|
||||
// - connection speed (?)
|
||||
// - app_info.json
|
||||
// - airplane mode service / BT HW state
|
||||
|
||||
// - API to detect that accessory is currently already connected to the phone?
|
||||
// - How to identify? iOS SDK does not expose addresses. Use DIS info? Fall
|
||||
// back to device name?
|
||||
|
||||
static void prv_handle_services_added(
|
||||
BLEClientServiceChangeHandler handler, BTDeviceInternal device, BTErrno status) {
|
||||
BLEService services[BLE_GATT_MAX_SERVICES_CHANGED];
|
||||
|
||||
uint8_t num_services = sys_ble_client_copy_services(device, services,
|
||||
BLE_GATT_MAX_SERVICES_CHANGED);
|
||||
|
||||
if (num_services != 0) {
|
||||
handler(device.opaque, BLEClientServicesAdded, services, num_services, status);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (PBL-22086): We should really make this easier to do ...
|
||||
// We can't directly dereference the service discovery info pointer from third
|
||||
// party apps because it was malloc'ed from the kernel heap. Instead copy the
|
||||
// info that is used onto a variable which has been allocated from the stack.
|
||||
DEFINE_SYSCALL(void, sys_get_service_discovery_info, const PebbleBLEGATTClientServiceEvent *e,
|
||||
PebbleBLEGATTClientServiceEventInfo *info) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
// Note: if we start storing services, we will need to update the size
|
||||
syscall_assert_userspace_buffer(info, sizeof(*info));
|
||||
}
|
||||
|
||||
*info = (PebbleBLEGATTClientServiceEventInfo) {
|
||||
.type = e->info->type,
|
||||
.device = e->info->device,
|
||||
.status = e->info->status
|
||||
};
|
||||
}
|
||||
|
||||
static void prv_handle_service_change(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const BLEClientServiceChangeHandler handler =
|
||||
ble_app_state->gatt_service_change_handler;
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
PebbleBLEGATTClientServiceEventInfo info;
|
||||
sys_get_service_discovery_info((const PebbleBLEGATTClientServiceEvent *)e, &info);
|
||||
|
||||
switch (info.type) {
|
||||
case PebbleServicesAdded:
|
||||
prv_handle_services_added(handler, info.device, info.status);
|
||||
break;
|
||||
case PebbleServicesRemoved:
|
||||
// TODO (PBL-22087): This is suboptimal. Before we release the BLE API, we should
|
||||
// either communicate to the App all the handles which have changed or
|
||||
// allow the getters for removed services to still work for the duration
|
||||
// of the callback. For now just force a full handle flush and then resync the app
|
||||
handler(info.device.opaque, BLEClientServicesInvalidateAll, NULL, 0, info.status);
|
||||
prv_handle_services_added(handler, info.device, info.status);
|
||||
break;
|
||||
case PebbleServicesInvalidateAll:
|
||||
handler(info.device.opaque, BLEClientServicesInvalidateAll, NULL, 0, info.status);
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*GenericReadHandler)(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset,
|
||||
BLEGATTError error);
|
||||
|
||||
static void prv_consume_read_response(const PebbleBLEGATTClientEvent *e,
|
||||
GenericReadHandler handler) {
|
||||
uint8_t *value = NULL;
|
||||
uint16_t value_length = e->value_length;
|
||||
const uintptr_t object_ref = e->object_ref;
|
||||
BLEGATTError gatt_error = e->gatt_error;
|
||||
|
||||
// Read Responses / Notifications with 0 length data must not be attempted to be consumed
|
||||
if (value_length) {
|
||||
value = (uint8_t *) applib_malloc(value_length);
|
||||
if (!value) {
|
||||
gatt_error = BLEGATTErrorLocalInsufficientResources;
|
||||
value_length = 0;
|
||||
}
|
||||
// If there is a read response, we *must* consume it,
|
||||
// otherwise the events and associated awaiting responses will
|
||||
// get out of sync with each other.
|
||||
sys_ble_client_consume_read(object_ref, value, &value_length);
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
handler(object_ref, value, value_length, 0 /* value_offset (future proofing) */, gatt_error);
|
||||
}
|
||||
applib_free(value);
|
||||
}
|
||||
|
||||
static void prv_consume_notifications(const PebbleBLEGATTClientEvent *e,
|
||||
GenericReadHandler handler) {
|
||||
uint8_t *value = NULL;
|
||||
BLEGATTError gatt_error = e->gatt_error;
|
||||
|
||||
uint16_t heap_buffer_size = 0;
|
||||
uint16_t value_length = 0;
|
||||
bool has_more = sys_ble_client_get_notification_value_length(&value_length);
|
||||
while (has_more) {
|
||||
if (heap_buffer_size < value_length) {
|
||||
const uint16_t new_heap_buffer_size = MIN(value_length, 64 /* arbitrary min size.. */);
|
||||
applib_free(value);
|
||||
value = (uint8_t *) applib_malloc(new_heap_buffer_size);
|
||||
heap_buffer_size = value ? new_heap_buffer_size : 0;
|
||||
}
|
||||
if (!value) {
|
||||
gatt_error = BLEGATTErrorLocalInsufficientResources;
|
||||
value_length = 0;
|
||||
}
|
||||
uintptr_t object_ref;
|
||||
// Consume, even if we didn't have enough memory, this will eat the notification and free up
|
||||
// the space in the buffer.
|
||||
const uint16_t next_value_length = sys_ble_client_consume_notification(&object_ref,
|
||||
value, &value_length,
|
||||
&has_more);
|
||||
if (handler) {
|
||||
handler(object_ref, value, value_length, 0 /* value_offset (future proofing) */, gatt_error);
|
||||
}
|
||||
|
||||
value_length = next_value_length;
|
||||
}
|
||||
|
||||
applib_free(value);
|
||||
}
|
||||
|
||||
static void prv_handle_notifications(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
prv_consume_notifications(e, ble_app_state->gatt_characteristic_read_handler);
|
||||
}
|
||||
|
||||
static void prv_handle_characteristic_read(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
prv_consume_read_response(e, ble_app_state->gatt_characteristic_read_handler);
|
||||
}
|
||||
|
||||
static void prv_handle_characteristic_write(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const BLEClientWriteHandler handler = ble_app_state->gatt_characteristic_write_handler;
|
||||
if (handler) {
|
||||
handler(e->object_ref, e->gatt_error);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_characteristic_subscribe(const PebbleBLEGATTClientEvent *e) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "TODO: GATT Client Event, subtype=%u", e->subtype);
|
||||
}
|
||||
|
||||
static void prv_handle_descriptor_read(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
prv_consume_read_response(e, ble_app_state->gatt_descriptor_read_handler);
|
||||
}
|
||||
|
||||
static void prv_handle_descriptor_write(const PebbleBLEGATTClientEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
const BLEClientWriteDescriptorHandler handler = ble_app_state->gatt_descriptor_write_handler;
|
||||
if (handler) {
|
||||
handler(e->object_ref, e->gatt_error);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_buffer_empty(const PebbleBLEGATTClientEvent *e) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
typedef void(*PrvHandler)(const PebbleBLEGATTClientEvent *);
|
||||
|
||||
static PrvHandler prv_handler_for_subtype(
|
||||
PebbleBLEGATTClientEventType event_subtype) {
|
||||
if (event_subtype >= PebbleBLEGATTClientEventTypeNum) {
|
||||
WTF;
|
||||
}
|
||||
// MT: This is a bit smaller in code size than a switch():
|
||||
static const PrvHandler handler[] = {
|
||||
[PebbleBLEGATTClientEventTypeServiceChange] = prv_handle_service_change,
|
||||
[PebbleBLEGATTClientEventTypeCharacteristicRead] = prv_handle_characteristic_read,
|
||||
[PebbleBLEGATTClientEventTypeNotification] = prv_handle_notifications,
|
||||
[PebbleBLEGATTClientEventTypeCharacteristicWrite] = prv_handle_characteristic_write,
|
||||
[PebbleBLEGATTClientEventTypeCharacteristicSubscribe] = prv_handle_characteristic_subscribe,
|
||||
[PebbleBLEGATTClientEventTypeDescriptorRead] = prv_handle_descriptor_read,
|
||||
[PebbleBLEGATTClientEventTypeDescriptorWrite] = prv_handle_descriptor_write,
|
||||
[PebbleBLEGATTClientEventTypeBufferEmpty] = prv_handle_buffer_empty,
|
||||
};
|
||||
return handler[event_subtype];
|
||||
}
|
||||
|
||||
// Exported for ble_app_support.c
|
||||
void ble_client_handle_event(PebbleEvent *e) {
|
||||
const PebbleBLEGATTClientEvent *gatt_event = &e->bluetooth.le.gatt_client;
|
||||
prv_handler_for_subtype(gatt_event->subtype)(gatt_event);
|
||||
}
|
||||
|
||||
static BTErrno prv_set_handler(void *new_handler, off_t struct_offset_bytes) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
typedef void (*BLEGenericHandler)(void);
|
||||
BLEGenericHandler *handler_storage =
|
||||
(BLEGenericHandler *)(((uint8_t *) ble_app_state) + struct_offset_bytes);
|
||||
|
||||
const bool had_previous_handler = (*handler_storage == NULL);
|
||||
*handler_storage = (BLEGenericHandler) new_handler;
|
||||
|
||||
if (had_previous_handler) {
|
||||
if (new_handler) {
|
||||
if (ble_app_state->gatt_client_num_handlers++ == 0) {
|
||||
// First GATT handler to be registered.
|
||||
// Subscribe to GATT Client event service:
|
||||
event_service_client_subscribe(&ble_app_state->gatt_client_service_info);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!new_handler) {
|
||||
if (--ble_app_state->gatt_client_num_handlers == 0) {
|
||||
// Last GATT handler to be de-registered.
|
||||
// Unsubscribe from GATT Client event service:
|
||||
event_service_client_unsubscribe(&ble_app_state->gatt_client_service_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_service_filter(const Uuid service_uuids[],
|
||||
uint8_t num_uuids) {
|
||||
// TODO
|
||||
return 0;
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_service_change_handler(BLEClientServiceChangeHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_service_change_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_read_handler(BLEClientReadHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_characteristic_read_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_write_response_handler(BLEClientWriteHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_characteristic_write_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_subscribe_handler(BLEClientSubscribeHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_characteristic_subscribe_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_buffer_empty_handler(BLEClientBufferEmptyHandler empty_handler) {
|
||||
// TODO
|
||||
return BTErrnoOther;
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_descriptor_write_handler(BLEClientWriteDescriptorHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_descriptor_write_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
||||
|
||||
BTErrno ble_client_set_descriptor_read_handler(BLEClientReadDescriptorHandler handler) {
|
||||
const off_t offset = offsetof(BLEAppState, gatt_descriptor_read_handler);
|
||||
return prv_set_handler(handler, offset);
|
||||
}
|
345
src/fw/applib/bluetooth/ble_client.h
Normal file
345
src/fw/applib/bluetooth/ble_client.h
Normal file
|
@ -0,0 +1,345 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
typedef enum {
|
||||
BLEClientServicesAdded,
|
||||
BLEClientServicesRemoved,
|
||||
BLEClientServicesInvalidateAll
|
||||
} BLEClientServiceChangeUpdate;
|
||||
|
||||
//! Callback that is called when the services on a remote device that are
|
||||
//! available to the application have changed. It gets called when:
|
||||
//! + the initial discovery of services completes
|
||||
//! + a new service has been discovered on the device after initial discovery
|
||||
//! + a service has been removed
|
||||
//! + the remote device has disconnected
|
||||
//!
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the service exposing the characteristic or descriptor is removed or
|
||||
//! all services are invalidated* or until application is terminated.
|
||||
//! @note References to services, characteristics and descriptors for the
|
||||
//! specified device, that have been obtained in a previous callback to the
|
||||
//! handler, are no longer valid. The application is responsible for cleaning up
|
||||
//! these references.
|
||||
//!
|
||||
//! @param device The device associated with the service discovery process
|
||||
//! @param update_type Defines what type of service update is being received
|
||||
//! @param services Array of pointers to the discovered services. The array will
|
||||
//! only contain Service UUIDs that matches the filter that was passed into the
|
||||
//! ble_client_discover_services_and_characteristics() call.
|
||||
//! @param num_services The number of discovered services matching the criteria.
|
||||
//! @param status BTErrnoOK if the service discovery was completed successfully.
|
||||
//! If the device was disconnected, BTErrnoConnectionTimeout,
|
||||
//! BTErrnoRemotelyTerminated, BTErrnoLocallyTerminatedBySystem or
|
||||
//! BTErrnoLocallyTerminatedByApp will specify the reason of the disconnection.
|
||||
//! The number of services will be zero upon a disconnection.
|
||||
typedef void (*BLEClientServiceChangeHandler)(BTDevice device,
|
||||
BLEClientServiceChangeUpdate update_type,
|
||||
const BLEService services[],
|
||||
uint8_t num_services,
|
||||
BTErrno status);
|
||||
|
||||
//! Registers the callback that handles service changes. After the call to
|
||||
//! this function returns, the application should be ready to handle calls to
|
||||
//! the handler.
|
||||
//! @param handler Pointer to the function that will handle the service changes
|
||||
//! @return BTErrnoOK if the handler was registered successfully,
|
||||
//! or TODO....
|
||||
BTErrno ble_client_set_service_change_handler(BLEClientServiceChangeHandler handler);
|
||||
|
||||
//! Registers the filter list of Service UUIDs.
|
||||
//! @param service_uuids An array of the Service UUIDs that the application is
|
||||
//! interested in and the system should filter by. Passing NULL will discover
|
||||
//! all services on the device.
|
||||
//! @param num_uuids The number of Uuid`s in the service_uuids array. Ignored
|
||||
//! when NULL is passed for the service_uuids argument.
|
||||
//! @return BTErrnoOK if the filter was set up successfully,
|
||||
//! or TODO....
|
||||
BTErrno ble_client_set_service_filter(const Uuid service_uuids[],
|
||||
uint8_t num_uuids);
|
||||
|
||||
//! Starts a discovery of services and characteristics on a remote device.
|
||||
//! The discovered services will be delivered to the application through the
|
||||
//! BLEClientServiceChangeHandler. The results will be filtered with the list
|
||||
//! of Service UUIDs as configured with ble_client_set_service_filter().
|
||||
//! @param device The device for which to perform service discovery.
|
||||
//! @return BTErrnoOK if the service discovery started successfully,
|
||||
//! BTErrnoInvalidParameter if the device was not connected,
|
||||
//! BTErrnoInvalidState if service discovery was already on-going, or
|
||||
//! an internal error otherwise (>= BTErrnoInternalErrorBegin).
|
||||
BTErrno ble_client_discover_services_and_characteristics(BTDevice device);
|
||||
|
||||
//! Different subscription types that can be used with ble_client_subscribe()
|
||||
typedef enum {
|
||||
//! No subscription.
|
||||
BLESubscriptionNone = 0,
|
||||
//! Notification subscription.
|
||||
BLESubscriptionNotifications = (1 << 0),
|
||||
//! Indication subscription.
|
||||
BLESubscriptionIndications = (1 << 1),
|
||||
//! Any subscription. Use this value with ble_client_subscribe(), in case
|
||||
//! the application does not care about the type of subscription. If both
|
||||
//! types are supported by the server, the notification subscription type
|
||||
//! will be used.
|
||||
BLESubscriptionAny = BLESubscriptionNotifications | BLESubscriptionIndications,
|
||||
} BLESubscription;
|
||||
|
||||
//! Callback to receive the characteristic value, resulting from either
|
||||
//! ble_client_read() and/or ble_client_subscribe().
|
||||
//! @param characteristic The characteristic of the received value
|
||||
//! @param value Byte-array containing the value
|
||||
//! @param value_length The number of bytes the byte-array contains
|
||||
//! @param value_offset The offset in bytes from the start of the characteristic
|
||||
//! value that has been read.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! read was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientReadHandler)(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the response to a written characteristic, resulting from
|
||||
//! ble_client_write().
|
||||
//! @param characteristic The characteristic that was written to.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! write was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientWriteHandler)(BLECharacteristic characteristic,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the confirmation of a subscription or unsubscription to
|
||||
//! characteristic value changes (notifications or indications).
|
||||
//! @param characteristic The characteristic for which the client is now
|
||||
//! (un)subscribed.
|
||||
//! @param subscription_type The type of subscription. If the client is now
|
||||
//! unsubscribed, the type will be BLESubscriptionNone.
|
||||
//! @param The error or status as returned by the remote server. If the
|
||||
//! (un)subscription was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientSubscribeHandler)(BLECharacteristic characteristic,
|
||||
BLESubscription subscription_type,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the event that the buffer for outbound data is empty.
|
||||
typedef void (*BLEClientBufferEmptyHandler)(void);
|
||||
|
||||
//! Registers the handler for characteristic value read operations.
|
||||
//! @param read_handler Pointer to the function that will handle callbacks
|
||||
//! with read characteristic values as result of calls to ble_client_read().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_read_handler(BLEClientReadHandler read_handler);
|
||||
|
||||
//! Registers the handler for characteristic value write (with response)
|
||||
//! operations.
|
||||
//! @param write_handler Pointer to the function that will handle callbacks
|
||||
//! for written characteristic values as result of calls to ble_client_write().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_write_response_handler(BLEClientWriteHandler write_handler);
|
||||
|
||||
//! Registers the handler for characteristic value subscribe operations.
|
||||
//! @param subscribe_handler Pointer to the function that will handle callbacks
|
||||
//! for (un)subscription confirmations as result of calls to
|
||||
//! ble_client_subscribe().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_subscribe_handler(BLEClientSubscribeHandler subscribe_handler);
|
||||
|
||||
//! Registers the handler to get called back when the buffer for outbound
|
||||
//! data is empty again.
|
||||
//! @param empty_handler Pointer to the function that will handle callbacks
|
||||
//! for "buffer empty" events.
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_buffer_empty_handler(BLEClientBufferEmptyHandler empty_handler);
|
||||
|
||||
//! Gets the maximum characteristic value size that can be written. This size
|
||||
//! can vary depending on the connected device.
|
||||
//! @param device The device for which to get the maximum characteristic value
|
||||
//! size.
|
||||
//! @return The maximum characteristic value size that can be written.
|
||||
uint16_t ble_client_get_maximum_value_length(BTDevice device);
|
||||
|
||||
//! Read the value of a characteristic.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientReadHandler handler. @see ble_client_set_read_handler.
|
||||
//! @param characteristic The characteristic for which to read the value
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_read(BLECharacteristic characteristic);
|
||||
|
||||
//! Write the value of a characterstic.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientWriteHandler handler. @see ble_client_set_write_response_handler.
|
||||
//! @param characteristic The characteristic for which to write the value
|
||||
//! @param value Buffer with the value to write
|
||||
//! @param value_length Number of bytes to write
|
||||
//! @note Values must not be longer than ble_client_get_maximum_value_length().
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_write(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length);
|
||||
|
||||
//! Write the value of a characterstic without response.
|
||||
//! @param characteristic The characteristic for which to write the value
|
||||
//! @param value Buffer with the value to write
|
||||
//! @param value_length Number of bytes to write
|
||||
//! @note Values must not be longer than ble_client_get_maximum_value_length().
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
//! If the buffer for outbound data was full, BTErrnoNotEnoughResources will
|
||||
//! be returned. When the buffer is emptied, the handler that is registered
|
||||
//! using ble_client_set_buffer_empty_handler() will be called.
|
||||
BTErrno ble_client_write_without_response(BLECharacteristic characteristic,
|
||||
const uint8_t *value,
|
||||
size_t value_length);
|
||||
|
||||
//! Subscribe to be notified or indicated of value changes of a characteristic.
|
||||
//!
|
||||
//! The value updates are delivered to the application through the
|
||||
//! BLEClientReadHandler, which should be registered before calling this
|
||||
//! function using ble_client_set_read_handler().
|
||||
//!
|
||||
//! There are two types of subscriptions: notifications and indications.
|
||||
//! For notifications there is no flow-control. This means that notifications
|
||||
//! can get dropped if the rate at which they are sent is too high. Conversely,
|
||||
//! each indication needs an acknowledgement from the receiver before the next
|
||||
//! one can get sent and is thus more reliable. The system performs
|
||||
//! acknowledgements to indications automatically. Applications do not need to
|
||||
//! worry about this, nor can they affect this.
|
||||
//! @param characteristic The characteristic to subscribe to.
|
||||
//! @param subscription_type The type of subscription to use.
|
||||
//! If BLESubscriptionAny is used as subscription_type and both types are
|
||||
//! supported by the server, the notification subscription type will be used.
|
||||
//! @note This call does not block and returns quickly. A callback to
|
||||
//! the BLEClientSubscribeHandler will happen at a later point in time, to
|
||||
//! report the success or failure of the subscription. This handler should be
|
||||
//! registered before calling this function using
|
||||
//! ble_client_set_subscribe_handler().
|
||||
//! @note Under the hood, this API writes to the Client Characteristic
|
||||
//! Configuration Descriptor's Notifications or Indications enabled/disabled
|
||||
//! bit.
|
||||
//! @return BTErrnoOK if the subscription request was sent sucessfully, or
|
||||
//! TODO...
|
||||
BTErrno ble_client_subscribe(BLECharacteristic characteristic,
|
||||
BLESubscription subscription_type);
|
||||
|
||||
|
||||
//! Callback to receive the descriptor value, resulting from a call to
|
||||
//! ble_client_read_descriptor().
|
||||
//! @param descriptor The descriptor of the received value
|
||||
//! @param value Byte-array containing the value
|
||||
//! @param value_length The number of bytes the byte-array contains
|
||||
//! @param value_offset The offset in bytes from the start of the descriptor
|
||||
//! value that has been read.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! read was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientReadDescriptorHandler)(BLEDescriptor descriptor,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Callback to handle the response to a written descriptor, resulting from
|
||||
//! ble_client_write_descriptor().
|
||||
//! @param descriptor The descriptor that was written to.
|
||||
//! @param error The error or status as returned by the remote server. If the
|
||||
//! write was successful, this remote server is supposed to send
|
||||
//! BLEGATTErrorSuccess.
|
||||
typedef void (*BLEClientWriteDescriptorHandler)(BLEDescriptor descriptor,
|
||||
BLEGATTError error);
|
||||
|
||||
//! Registers the handlers for descriptor value write operations.
|
||||
//! @param write_handler Pointer to the function that will handle callbacks
|
||||
//! for written descriptor values as result of calls to
|
||||
//! ble_client_write_descriptor().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_descriptor_write_handler(BLEClientWriteDescriptorHandler write_handler);
|
||||
|
||||
//! Registers the handlers for descriptor value read operations.
|
||||
//! @param read_handler Pointer to the function that will handle callbacks
|
||||
//! with read descriptor values as result of calls to
|
||||
//! ble_client_read_descriptor().
|
||||
//! @return BTErrnoOK if the handlers were successfully registered, or ... TODO
|
||||
BTErrno ble_client_set_descriptor_read_handler(BLEClientReadDescriptorHandler read_handler);
|
||||
|
||||
//! Write the value of a descriptor.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientWriteDescriptorHandler handler.
|
||||
//! @see ble_client_set_descriptor_write_handler.
|
||||
//! @param descriptor The descriptor for which to write the value
|
||||
//! @param value Buffer with the value to write
|
||||
//! @param value_length Number of bytes to write
|
||||
//! @note Values must not be longer than ble_client_get_maximum_value_length().
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_write_descriptor(BLEDescriptor descriptor,
|
||||
const uint8_t *value,
|
||||
size_t value_length);
|
||||
|
||||
//! Read the value of a descriptor.
|
||||
//! A call to this function will result in a callback to the registered
|
||||
//! BLEClientReadDescriptorHandler handler.
|
||||
//! @see ble_client_set_descriptor_read_handler.
|
||||
//! @param descriptor The descriptor for which to read the value
|
||||
//! @return BTErrnoOK if the operation was successfully started, or ... TODO
|
||||
BTErrno ble_client_read_descriptor(BLEDescriptor descriptor);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs would be:
|
||||
|
||||
|
||||
//! Opaque ATT request context
|
||||
typedef void * BLERequest;
|
||||
|
||||
typedef void (*BLEServerWriteHandler)(BLERequest request,
|
||||
BLECharacteristic characteristic,
|
||||
BTDevice remote_device,
|
||||
const uint8_t *value,
|
||||
size_t value_length,
|
||||
uint16_t value_offset);
|
||||
|
||||
typedef void (*BLEServerReadHandler)(BLECharacteristic characteristic,
|
||||
BTDevice remote_device,
|
||||
uint16_t value_offset);
|
||||
|
||||
typedef void (*BLEServerSubscribeHandler)(BLECharacteristic characteristic,
|
||||
BTDevice remote_device,
|
||||
BLESubscription subscription_type);
|
||||
|
||||
BTErrno ble_server_set_handlers(BLEServerReadHandler read_handler,
|
||||
BLEServerWriteHandler write_handler,
|
||||
BLEServerSubscribeHandler subscription_handler);
|
||||
|
||||
BTErrno ble_server_start_service(BLEService service);
|
||||
|
||||
BTErrno ble_server_stop_service(BLEService service);
|
||||
|
||||
BTErrno ble_server_respond_to_write(BLERequest request, BLEGATTError error);
|
||||
|
||||
BTErrno ble_server_respond_to_read(BLERequest request, BLEGATTError error,
|
||||
const uint8_t *value, size_t value_length,
|
||||
uint16_t value_offset);
|
||||
|
||||
BTErrno ble_server_send_update(BLECharacteristic characteristic,
|
||||
const uint8_t *value, size_t value_length);
|
||||
|
||||
BTErrno ble_server_send_update_selectively(BLECharacteristic characteristic,
|
||||
const uint8_t *value, size_t value_length,
|
||||
const BTDevice *devices, uint8_t num_devices);
|
43
src/fw/applib/bluetooth/ble_descriptor.h
Normal file
43
src/fw/applib/bluetooth/ble_descriptor.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Gets the UUID for a descriptor.
|
||||
//! @param descriptor The descriptor for which to get the UUID.
|
||||
//! @return The UUID of the descriptor
|
||||
Uuid ble_descriptor_get_uuid(BLEDescriptor descriptor);
|
||||
|
||||
//! Gets the characteristic for a descriptor.
|
||||
//! @param descriptor The descriptor for which to get the characteristic.
|
||||
//! @return The characteristic
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
BLECharacteristic ble_descriptor_get_characteristic(BLEDescriptor descriptor);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs would be:
|
||||
|
||||
|
||||
BLEDescriptor ble_descriptor_create(const Uuid *uuid,
|
||||
BLEAttributeProperty properties);
|
||||
|
||||
BTErrno ble_descriptor_destroy(BLEDescriptor descriptor);
|
17
src/fw/applib/bluetooth/ble_device.c
Normal file
17
src/fw/applib/bluetooth/ble_device.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_device.h"
|
33
src/fw/applib/bluetooth/ble_device.h
Normal file
33
src/fw/applib/bluetooth/ble_device.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Copies the devices that are known to the system. This set includes all
|
||||
//! paired devices (connected or not) and devices for which there is a Bluetooth
|
||||
//! connection to the system (but not necessarily paired and not necessarily
|
||||
//! connected to the application).
|
||||
//! @param[out] devices_out An array of BTDevice`s into which the known
|
||||
//! devices will be copied.
|
||||
//! @param[in,out] num_devices_out In: the size of the devices_out array.
|
||||
//! Out: the number of BTDevice`s that were copied.
|
||||
//! @return The total number of known devices. This might be a larger
|
||||
//! number than num_devices_out will contain, if the passed array was not large
|
||||
//! enough to hold all the connected devices.
|
||||
uint8_t ble_device_copy_known_devices(BTDevice *devices_out,
|
||||
uint8_t *num_devices_out);
|
145
src/fw/applib/bluetooth/ble_ibeacon.c
Normal file
145
src/fw/applib/bluetooth/ble_ibeacon.c
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_ibeacon.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Apple's iBeacon AD DATA format.
|
||||
//! The byte-order of Apple's fields (uuid, major and minor) is Big Endian (!!!)
|
||||
//! @see Apple's docs for more info: http://goo.gl/iOrnpj
|
||||
//! @see StackOverflow distance/accuracy calculations: http://goo.gl/yH0ubM
|
||||
static const uint16_t COMPANY_ID_APPLE = 0x004c;
|
||||
static const uint8_t APPLE_TYPE_IBEACON = 0x02;
|
||||
static const uint8_t APPLE_IBEACON_LENGTH = 0x15;
|
||||
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
//! @see APPLE_AD_TYPE_IBEACON
|
||||
uint8_t type;
|
||||
|
||||
//! @see APPLE_IBEACON_LENGTH
|
||||
uint8_t length;
|
||||
|
||||
//! The application "proximityUUID" of the iBeacon. Generally, multiple
|
||||
//! iBeacons share one UUID and an (iOS) app scans for one particular UUID.
|
||||
uint8_t uuid[16];
|
||||
|
||||
//! The most significant value in the beacon.
|
||||
uint16_t major;
|
||||
|
||||
//! The least significant value in the beacon.
|
||||
uint16_t minor;
|
||||
|
||||
//! The calibrated transmit power.
|
||||
int8_t calibrated_tx_power;
|
||||
} AdDataManufacturerSpecificAppleiBeacon;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Accessors
|
||||
|
||||
Uuid ble_ibeacon_get_uuid(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->uuid;
|
||||
}
|
||||
|
||||
uint16_t ble_ibeacon_get_major(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->major;
|
||||
}
|
||||
|
||||
uint16_t ble_ibeacon_get_minor(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->minor;
|
||||
}
|
||||
|
||||
uint16_t ble_ibeacon_get_distance_cm(const BLEiBeacon *ibeacon) {
|
||||
return ibeacon->distance_cm;
|
||||
}
|
||||
|
||||
BLEiBeacon *ble_ibeacon_create_from_ad_data(const BLEAdData *ad,
|
||||
int8_t rssi) {
|
||||
// Note, not yet exported to 3rd party apps so no padding necessary
|
||||
BLEiBeacon *ibeacon = applib_malloc(sizeof(BLEiBeacon));
|
||||
if (ibeacon && !ble_ibeacon_parse(ad, rssi, ibeacon)) {
|
||||
// Failed to parse.
|
||||
applib_free(ibeacon);
|
||||
ibeacon = NULL;
|
||||
}
|
||||
return ibeacon;
|
||||
}
|
||||
|
||||
void ble_ibeacon_destroy(BLEiBeacon *ibeacon) {
|
||||
applib_free(ibeacon);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Below is the iBeacon advertisement parsing code.
|
||||
|
||||
static uint16_t calculate_distance_cm(int8_t tx_power, int8_t rssi) {
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! iBeacon Advertisement Data parser
|
||||
bool ble_ibeacon_parse(const BLEAdData *ad, int8_t rssi,
|
||||
BLEiBeacon *ibeacon_out) {
|
||||
uint16_t company_id = 0;
|
||||
AdDataManufacturerSpecificAppleiBeacon raw_ibeacon;
|
||||
const size_t size_copied =
|
||||
ble_ad_copy_manufacturer_specific_data(ad, &company_id,
|
||||
(uint8_t *) &raw_ibeacon,
|
||||
sizeof(raw_ibeacon));
|
||||
if (size_copied != sizeof(raw_ibeacon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (company_id == COMPANY_ID_APPLE &&
|
||||
raw_ibeacon.type == APPLE_TYPE_IBEACON &&
|
||||
raw_ibeacon.length == APPLE_IBEACON_LENGTH) {
|
||||
|
||||
const int8_t tx_power = raw_ibeacon.calibrated_tx_power;
|
||||
*ibeacon_out = (const BLEiBeacon) {
|
||||
.uuid = UuidMakeFromBEBytes(raw_ibeacon.uuid),
|
||||
.major = ntohs(raw_ibeacon.major),
|
||||
.minor = ntohs(raw_ibeacon.minor),
|
||||
.distance_cm = calculate_distance_cm(tx_power, rssi),
|
||||
.rssi = rssi,
|
||||
.calibrated_tx_power = tx_power,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! iBeacon Advertisement Data composer
|
||||
bool ble_ibeacon_compose(const BLEiBeacon *ibeacon_in, BLEAdData *ad_out) {
|
||||
AdDataManufacturerSpecificAppleiBeacon raw_ibeacon = {
|
||||
.type = APPLE_TYPE_IBEACON,
|
||||
.length = APPLE_IBEACON_LENGTH,
|
||||
// Major/Minor are part of Apple's iBeacon spec and are Big Endian!
|
||||
.major = htons(ibeacon_in->major),
|
||||
.minor = htons(ibeacon_in->minor),
|
||||
.calibrated_tx_power = ibeacon_in->calibrated_tx_power,
|
||||
};
|
||||
// Uuid is stored Big Endian on Pebble, so just copy over:
|
||||
memcpy(&raw_ibeacon.uuid, &ibeacon_in->uuid, sizeof(Uuid));
|
||||
|
||||
return ble_ad_set_manufacturer_specific_data(ad_out, COMPANY_ID_APPLE,
|
||||
(uint8_t *) &raw_ibeacon,
|
||||
sizeof(raw_ibeacon));
|
||||
}
|
105
src/fw/applib/bluetooth/ble_ibeacon.h
Normal file
105
src/fw/applib/bluetooth/ble_ibeacon.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
#include "applib/bluetooth/ble_ad_parse.h"
|
||||
|
||||
//! Size in bytes of the iBeacon advertisement data, including the length and
|
||||
//! AD Type bytes.
|
||||
#define IBEACON_ADVERTISEMENT_DATA_SIZE (27)
|
||||
|
||||
//! Data structure representing an iBeacon advertisement.
|
||||
typedef struct {
|
||||
//! The application UUID that the iBeacon advertised. In iOS' CoreBluetooth,
|
||||
//! this corresponds to the "proximityUUID" property of instances of CLBeacon.
|
||||
Uuid uuid;
|
||||
|
||||
//! Custom value, most significant part.
|
||||
uint16_t major;
|
||||
|
||||
//! Custom value, least significant part.
|
||||
uint16_t minor;
|
||||
|
||||
//! Estimated distance to the iBeacon in centimeters. In iOS' CoreBluetooth,
|
||||
//! this corresponds to the "accuracy" property of instances of CLBeacon.
|
||||
uint16_t distance_cm;
|
||||
|
||||
//! The received signal strength from the iBeacon, in decibels.
|
||||
int8_t rssi;
|
||||
|
||||
//! The calibrated power of the iBeacon. This is the RSSI measured at 1 meter
|
||||
//! distance from the iBeacon. The iBeacon transmits this information in its
|
||||
//! advertisment. Using this and the actual RSSI, the distance is estimated.
|
||||
int8_t calibrated_tx_power;
|
||||
} BLEiBeacon;
|
||||
|
||||
//! Gets the UUID of the iBeacon.
|
||||
//! @param The iBeacon
|
||||
//! @return The UUID that the iBeacon advertised. In iOS' CoreBluetooth,
|
||||
//! this corresponds to the "proximityUUID" property of instances of CLBeacon.
|
||||
Uuid ble_ibeacon_get_uuid(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Gets the major value of the iBeacon.
|
||||
//! @param The iBeacon
|
||||
//! @return The major, custom value.
|
||||
uint16_t ble_ibeacon_get_major(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Gets the minor value of the iBeacon.
|
||||
//! @param The iBeacon
|
||||
//! @return The minor, custom value.
|
||||
uint16_t ble_ibeacon_get_minor(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Gets the estimated distance to the iBeacon, in centimeters.
|
||||
//! @param The iBeacon
|
||||
//! @return The estimated distance in centimeters.
|
||||
uint16_t ble_ibeacon_get_distance_cm(const BLEiBeacon *ibeacon);
|
||||
|
||||
//! Create BLEiBeacon from advertisement data.
|
||||
//! @param ad Advertisement data, as acquired from the BLEScanHandler callback.
|
||||
//! @param rssi The RSSI of the advertisement, as acquired from the
|
||||
//! BLEScanHandler callback.
|
||||
//! @return BLEiBeacon object if iBeacon data is found, or NULL if the
|
||||
//! advertisement data did not contain valid iBeacon data.
|
||||
BLEiBeacon *ble_ibeacon_create_from_ad_data(const BLEAdData *ad,
|
||||
int8_t rssi);
|
||||
|
||||
//! Destroys an BLEiBeacon object and frees its resources that were allocated
|
||||
//! earlier by ble_ibeacon_create_from_ad_data().
|
||||
//! @param ibeacon Reference to the BLEiBeacon to destroy.
|
||||
void ble_ibeacon_destroy(BLEiBeacon *ibeacon);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal iBeacon Advertisement Data parser
|
||||
//! @param ad The raw advertisement data
|
||||
//! @param rssi The RSSI of the advertisement
|
||||
//! @param[out] ibeacon_out Will contain the parsed iBeacon data if the call
|
||||
//! returns true.
|
||||
//! @return true if the data element was succesfully parsed as iBeacon,
|
||||
//! false if the data element could not be parsed as iBeacon.
|
||||
bool ble_ibeacon_parse(const BLEAdData *ad, int8_t rssi,
|
||||
BLEiBeacon *ibeacon_out);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
//! Internal iBeacon Advertisement Data serializer
|
||||
//! @param ibeacon_in The iBeacon structure to serialize. The rssi and
|
||||
//! distance_cm fields are ignored because they are only valid for received
|
||||
//! iBeacon packets.
|
||||
//! @param[out] ad_out The advertisement payload to write the data into.
|
||||
//! @return true if the iBeacon data was written successfully.
|
||||
bool ble_ibeacon_compose(const BLEiBeacon *ibeacon_in, BLEAdData *ad_out);
|
109
src/fw/applib/bluetooth/ble_scan.c
Normal file
109
src/fw/applib/bluetooth/ble_scan.c
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_scan.h"
|
||||
|
||||
#include "ble_ad_parse.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "comm/ble/gap_le_scan.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
|
||||
|
||||
void ble_scan_handle_event(PebbleEvent *e) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (!ble_app_state->scan_handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the same buffer size as the kernel itself:
|
||||
uint8_t *buffer = (uint8_t *) applib_malloc(GAP_LE_SCAN_REPORTS_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
APP_LOG(LOG_LEVEL_ERROR, "Need %u bytes of heap for ble_scan_start()",
|
||||
GAP_LE_SCAN_REPORTS_BUFFER_SIZE);
|
||||
return;
|
||||
}
|
||||
uint16_t size = GAP_LE_SCAN_REPORTS_BUFFER_SIZE;
|
||||
sys_ble_consume_scan_results(buffer, &size);
|
||||
|
||||
if (size == 0) {
|
||||
goto finally;
|
||||
}
|
||||
|
||||
// Walk all the reports in the buffer:
|
||||
const uint8_t *cursor = buffer;
|
||||
while (cursor < buffer + size) {
|
||||
const GAPLERawAdReport *report = (GAPLERawAdReport *)cursor;
|
||||
|
||||
const BTDeviceInternal device = (const BTDeviceInternal) {
|
||||
.address = report->address.address,
|
||||
.is_classic = false,
|
||||
.is_random_address = report->is_random_address,
|
||||
};
|
||||
|
||||
// Call the scan handler for each report:
|
||||
ble_app_state->scan_handler(device.opaque, report->rssi, &report->payload);
|
||||
|
||||
const size_t report_length = sizeof(GAPLERawAdReport) +
|
||||
report->payload.ad_data_length +
|
||||
report->payload.scan_resp_data_length;
|
||||
cursor += report_length;
|
||||
}
|
||||
|
||||
finally:
|
||||
applib_free(buffer);
|
||||
}
|
||||
|
||||
BTErrno ble_scan_start(BLEScanHandler handler) {
|
||||
if (!handler) {
|
||||
return (BTErrnoInvalidParameter);
|
||||
}
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (ble_app_state->scan_handler) {
|
||||
return (BTErrnoInvalidState);
|
||||
}
|
||||
const bool result = sys_ble_scan_start();
|
||||
if (!result) {
|
||||
return BTErrnoOther;
|
||||
}
|
||||
ble_app_state->scan_handler = handler;
|
||||
event_service_client_subscribe(&ble_app_state->scan_service_info);
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
BTErrno ble_scan_stop(void) {
|
||||
BLEAppState *ble_app_state = app_state_get_ble_app_state();
|
||||
if (!ble_app_state->scan_handler) {
|
||||
return (BTErrnoInvalidState);
|
||||
}
|
||||
const bool result = sys_ble_scan_stop();
|
||||
if (!result) {
|
||||
return BTErrnoOther;
|
||||
}
|
||||
event_service_client_unsubscribe(&ble_app_state->scan_service_info);
|
||||
ble_app_state->scan_handler = NULL;
|
||||
return BTErrnoOK;
|
||||
}
|
||||
|
||||
bool ble_scan_is_scanning(void) {
|
||||
return sys_ble_scan_is_scanning();
|
||||
}
|
53
src/fw/applib/bluetooth/ble_scan.h
Normal file
53
src/fw/applib/bluetooth/ble_scan.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Callback that is called for each advertisment that is found while scanning
|
||||
//! using ble_scan_start().
|
||||
//! @param device The device from which the advertisment originated.
|
||||
//! @param rssi The RSSI (Received Signal Strength Indication) of the
|
||||
//! advertisement.
|
||||
//! @param advertisement_data The payload of the advertisement. When there was
|
||||
//! a scan response, this payload will contain the data of the scan response
|
||||
//! as well. The information in the payload can be accessed using the
|
||||
//! ble_ad_... functions, @see for example ble_ad_copy_local_name() and
|
||||
//! ble_ad_includes_service().
|
||||
//! @note The advertisement_data is cleaned up by the system automatically
|
||||
//! immediately after returning from this callback. Do not keep around
|
||||
//! any long-lived references around to the advertisement_data.
|
||||
//! @note Do not use ble_ad_destroy() on the advertisement_data.
|
||||
typedef void (*BLEScanHandler)(BTDevice device,
|
||||
int8_t rssi,
|
||||
const BLEAdData *advertisement_data);
|
||||
|
||||
//! Start scanning for advertisements. Pebble will scan actively, meaning it
|
||||
//! will perform scan requests whenever the advertisement is scannable.
|
||||
//! @param handler The callback to handle the found advertisments. It must not
|
||||
//! be NULL.
|
||||
//! @return BTErrnoOK if scanning started successfully, BTErrnoInvalidParameter
|
||||
//! if the handler was invalid or BTErrnoInvalidState if scanning had already
|
||||
//! been started.
|
||||
BTErrno ble_scan_start(BLEScanHandler handler);
|
||||
|
||||
//! Stop scanning for advertisements.
|
||||
//! @return BTErrnoOK if scanning stopped successfully, or TODO...
|
||||
BTErrno ble_scan_stop(void);
|
||||
|
||||
//! @return True if the system is scanning for advertisements or false if not.
|
||||
bool ble_scan_is_scanning(void);
|
17
src/fw/applib/bluetooth/ble_security.c
Normal file
17
src/fw/applib/bluetooth/ble_security.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_security.h"
|
94
src/fw/applib/bluetooth/ble_security.h
Normal file
94
src/fw/applib/bluetooth/ble_security.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Out-Of-Band additions
|
||||
|
||||
|
||||
//! "Out-of-Band" (OOB) is one of the mechanisms to exchange a shared secret
|
||||
//! during a pairing procedure between two devices. "PIN" and "Just Works" are
|
||||
//! the two other exchange mechanisms that the Bluetooth 4.0 Specification
|
||||
//! defines, but both are susceptible to eavesdropping of the exchanged keys.
|
||||
//! OOB provides better protection against this, by offering a way to exchange
|
||||
//! the shared secret via a communications channel other than Bluetooth itself
|
||||
//! (hence the name "Out-of-Band"). Of course, this is only more secure if the
|
||||
//! channel through which the OOB data is exchanged itself is harder to
|
||||
//! eavesdrop.
|
||||
//!
|
||||
//! The exchanged OOB data is used as Temporary-Key (TK) to encrypt the
|
||||
//! connection during the one-time pairing information exchange. Part of this
|
||||
//! information exchange are Long-Term-Key(s) (LTK) that will be used upon
|
||||
//! successive reconnections. For more details, see Bluetooth 4.0 Specification,
|
||||
//! Volume 3, Part H, 2.3.5, "Pairing Algorithms".
|
||||
//!
|
||||
//! The OOB APIs enable the application to provide the system with OOB data.
|
||||
//! The application will need to indicate to the system for what devices it
|
||||
//! is capable of providing OOB data. Later, when a pairing procedure takes
|
||||
//! place with an OOB-enabled device, the system will ask the application to
|
||||
//! provide that OOB data.
|
||||
//!
|
||||
//! It is up to the application and the manufacturer of the device how the OOB
|
||||
//! data is exchanged between the application and the remote device. Examples of
|
||||
//! how this can be done:
|
||||
//! - The application could generate the OOB data and show a QR code containing
|
||||
//! the data on the screen of the Pebble that is then read by the device.
|
||||
//! - If the device is connected to the Internet, the OOB data could be
|
||||
//! provisioned to Pebble via a web service. The application would use the
|
||||
//! JavaScript APIs to fetch the data from the web service and transfer the
|
||||
//! data to the application on the watch using the AppMessage APIs.
|
||||
|
||||
|
||||
//! Pointer to a function that can provide Out-Of-Band keys.
|
||||
//! @see ble_security_set_oob_handler() and ble_security_enable_oob()
|
||||
//! @param device The device for which the OOB key needs to be provided
|
||||
//! @param oob_key_buffer_out The buffer into which the OOB key should be
|
||||
//! written.
|
||||
//! @param oob_key_buffer_size The size of the buffer in bytes. Currently only
|
||||
//! keys of 128-bit (16 byte) size are supported.
|
||||
//! @return true if the OOB key was written or false if no OOB data could be
|
||||
//! provided for the device.
|
||||
typedef bool (*BLESecurityOOBHandler)(BTDevice device,
|
||||
uint8_t *oob_key_buffer_out,
|
||||
size_t oob_key_buffer_size);
|
||||
|
||||
//! Registers a permanent callback function that is responsible for providing
|
||||
//! Out-Of-Band (OOB) keys. The callback is guaranteed to get called only for
|
||||
//! devices for which the application has enabled OOB using
|
||||
//! ble_security_enable_oob(). The callback will get called by the system during
|
||||
//! a pairing procedure, but only if the remote device indicated to have OOB
|
||||
//! data as well.
|
||||
//! @param oob_handler Pointer to the function that will provide OOB key data.
|
||||
//! @return BTErrnoOK if the call was successful, or TODO...
|
||||
BTErrno ble_security_set_oob_handler(BLESecurityOOBHandler oob_handler);
|
||||
|
||||
//! Enable or disable Out-Of-Band pairing for the device.
|
||||
//! This function is a way to indicate to the system that the application has
|
||||
//! Out-Of-Band data that can be used when pairing with a particular device.
|
||||
//! @note The application is encouraged to configure OOB as soon as possible,
|
||||
//! *before* connecting to any devices. If the application supports OOB, but
|
||||
//! enables is *after* connecting, there is a chance that the remote requests to
|
||||
//! start pairing before your application has had the chance to enable OOB.
|
||||
//! @note After terminating the application, the system will automatically
|
||||
//! disable OOB for any devices it had enabled OOB for. Upon re-launching the
|
||||
//! application, it will need to re-enable OOB if required.
|
||||
//! @param device The device for which to enable or disable OOB
|
||||
//! @param enable Pass in true to enable OOB for the device, or false to disable
|
||||
//! @return BTErrnoOK if the call was successful, or TODO...
|
||||
BTErrno ble_security_enable_oob(BTDevice device, bool enable);
|
17
src/fw/applib/bluetooth/ble_service.c
Normal file
17
src/fw/applib/bluetooth/ble_service.c
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#include "ble_service.h"
|
88
src/fw/applib/bluetooth/ble_service.h
Normal file
88
src/fw/applib/bluetooth/ble_service.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bluetooth/bluetooth_types.h>
|
||||
|
||||
//! Gets the characteristics associated with a service.
|
||||
//! @param service The service for which to get the characteristics
|
||||
//! @param[out] characteristics_out An array of pointers to characteristics,
|
||||
//! into which the associated characteristics will be copied.
|
||||
//! @param num_characteristics The size of the characteristics_out array.
|
||||
//! @return The total number of characteristics for the service. This might be a
|
||||
//! larger number than num_in_out will contain, if the passed array was not
|
||||
//! large enough to hold all the pointers.
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
uint8_t ble_service_get_characteristics(BLEService service,
|
||||
BLECharacteristic characteristics_out[],
|
||||
uint8_t num_characteristics);
|
||||
|
||||
//! Gets the Service UUID of a service.
|
||||
//! @param service The service for which to get the Service UUID.
|
||||
//! @return The 128-bit Service UUID, or UUID_INVALID if the service reference
|
||||
//! was invalid.
|
||||
//! @note The returned UUID is always a 128-bit UUID, even if the device
|
||||
//! its interal GATT service database uses 16-bit or 32-bit Service UUIDs.
|
||||
//! @see bt_uuid_expand_16bit for a macro that converts 16-bit UUIDs to 128-bit
|
||||
//! equivalents.
|
||||
//! @see bt_uuid_expand_32bit for a macro that converts 32-bit UUIDs to 128-bit
|
||||
//! equivalents.
|
||||
Uuid ble_service_get_uuid(BLEService service);
|
||||
|
||||
//! Gets the device that hosts the service.
|
||||
//! @param service The service for which to find the device it belongs to.
|
||||
//! @return The device hosting the service, or an invalid device if the service
|
||||
//! reference was invalid. Use bt_device_is_invalid() to test whether the
|
||||
//! returned device is invalid.
|
||||
BTDevice ble_service_get_device(BLEService service);
|
||||
|
||||
//! Gets the services that are references by a service as "Included Service".
|
||||
//! @param service The service for which to get the included services
|
||||
//! @param[out] included_services_out An array of pointers to services,
|
||||
//! into which the included services will be copied.
|
||||
//! @param num_services the size of the included_services_out array.
|
||||
//! @return The total number of included services for the service. This might be
|
||||
//! a larger number than included_services_out can contain, if the passed array
|
||||
//! was not large enough to hold all the pointers.
|
||||
//! @note For convenience, the services are owned by the system and references
|
||||
//! to services, characteristics and descriptors are guaranteed to remain valid
|
||||
//! *until the BLEClientServiceChangeHandler is called again* or until
|
||||
//! application is terminated.
|
||||
uint8_t ble_service_get_included_services(BLEService service,
|
||||
BLEService included_services_out[],
|
||||
uint8_t num_services);
|
||||
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// (FUTURE / LATER / NOT SCOPED)
|
||||
// Just to see how symmetric the Server APIs could be:
|
||||
|
||||
// creates + adds to GATT DB (?)
|
||||
// Services aren't supposed to change. Pass everything into the 'create' call:
|
||||
BLEService ble_service_create(const Uuid *service_uuid,
|
||||
BLECharacteristic characteristics[],
|
||||
uint8_t num_characteristics);
|
||||
|
||||
void ble_service_set_included_services(BLEService service,
|
||||
BLEService included_services[],
|
||||
uint8_t num_included_services);
|
||||
|
||||
// removes from GATT DB (?) + destroys
|
||||
void ble_service_destroy(BLEService service);
|
Loading…
Add table
Add a link
Reference in a new issue