mirror of
https://github.com/google/pebble.git
synced 2025-11-22 15:30:55 -05:00
Import of the watch repository from Pebble
This commit is contained in:
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);
|
||||
}
|
||||
Reference in New Issue
Block a user