mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
[BLE Refactor] Implement iOS Scanning modules.
PiperOrigin-RevId: 455274324
This commit is contained in:
committed by
Copybara-Service
parent
cf85146096
commit
9a1ada9a56
@@ -53,13 +53,12 @@ bool BleV2Medium::StartScanning(const Uuid& service_uuid,
|
||||
[this](api::ble_v2::BlePeripheral& peripheral,
|
||||
BleAdvertisementData advertisement_data) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (peripherals_.contains(&peripheral)) {
|
||||
if (!peripherals_.contains(&peripheral)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "There is no need to callback due to peripheral impl="
|
||||
<< &peripheral << ", which already exists.";
|
||||
return;
|
||||
peripherals_.insert(&peripheral);
|
||||
}
|
||||
peripherals_.insert(&peripheral);
|
||||
BleV2Peripheral proxy(&peripheral);
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "New peripheral imp=" << &peripheral
|
||||
|
||||
@@ -12,16 +12,28 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#import "internal/platform/implementation/ios/Mediums/GNCMConnection.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class CBUUID;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* This handler is called on a discover when a nearby advertising endpoint is discovered.
|
||||
*/
|
||||
typedef void (^GNCMScanResultHandler)(NSString *serviceUUID, NSData *serviceData);
|
||||
typedef void (^GNCMScanResultHandler)(NSString *peripheralID, NSData *serviceData);
|
||||
|
||||
/**
|
||||
* This handler is called on a discovery when a nearby advertising endpoint is connected.
|
||||
*/
|
||||
typedef void (^GNCMGATTConnectionResultHandler)(NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* This handler is called on a discovery when a nearby advertising endpoint is connected. The input
|
||||
* is a discovered map of characteristic values.
|
||||
*/
|
||||
typedef void (^GNCMGATTDiscoverResultHandler)(
|
||||
NSDictionary<CBUUID *, NSData *> *_Nullable characteristicValues);
|
||||
|
||||
/**
|
||||
* GNCMBleCentral discovers devices advertising the specified service UUID via BLE (using the
|
||||
@@ -33,18 +45,49 @@ typedef void (^GNCMScanResultHandler)(NSString *serviceUUID, NSData *serviceData
|
||||
*/
|
||||
@interface GNCMBleCentral : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)init;
|
||||
|
||||
/**
|
||||
* Initializes an `GNCMBleCentral` object.
|
||||
* Starts scanning with service UUID.
|
||||
*
|
||||
* @param serviceUUID A string that uniquely identifies the scanning services to search for.
|
||||
* @param scanResultHandler The handler that is called when an endpoint advertising the service
|
||||
* UUID is discovered.
|
||||
*/
|
||||
- (instancetype)initWithServiceUUID:(NSString *)serviceUUID
|
||||
scanResultHandler:(GNCMScanResultHandler)scanResultHandler
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
- (BOOL)startScanningWithServiceUUID:(NSString *)serviceUUID
|
||||
scanResultHandler:(GNCMScanResultHandler)scanResultHandler;
|
||||
|
||||
/**
|
||||
* Sets up a GATT connection.
|
||||
*
|
||||
* @param peripheralID A string that uniquely identifies the peripheral.
|
||||
* @param gattConnectionResultHandler The handler that is called when an endpoint is connected.
|
||||
*/
|
||||
- (void)connectGattServerWithPeripheralID:(NSString *)peripheralID
|
||||
gattConnectionResultHandler:
|
||||
(GNCMGATTConnectionResultHandler)gattConnectionResultHandler;
|
||||
|
||||
/**
|
||||
* Discovers GATT service and its associated characteristics with values.
|
||||
*
|
||||
* @param serviceUUID A CBUUID for service to discover.
|
||||
* @param gattCharacteristics Array of CBUUID for characteristic to discover.
|
||||
* @param peripheralID A string that uniquely identifies the peripheral.
|
||||
* @param gattDiscoverResultHandler This handler is called on a discovery for a discovered map of
|
||||
* characteristic values when a nearby advertising endpoint is
|
||||
* connected.
|
||||
*/
|
||||
- (void)discoverGattService:(CBUUID *)serviceUUID
|
||||
gattCharacteristics:(NSArray<CBUUID *> *)characteristicUUIDs
|
||||
peripheralID:(NSString *)peripheralID
|
||||
gattDiscoverResultHandler:(GNCMGATTDiscoverResultHandler)gattDiscoverResultHandler;
|
||||
|
||||
/**
|
||||
* Disconnects GATT connection.
|
||||
*
|
||||
* @param peripheralID A string that uniquely identifies the peripheral.
|
||||
*/
|
||||
- (void)disconnectGattServiceWithPeripheralID:(NSString *)peripheralID;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -14,12 +14,63 @@
|
||||
|
||||
#import "internal/platform/implementation/ios/Mediums/Ble/GNCMBleCentral.h"
|
||||
|
||||
#include <CoreBluetooth/CoreBluetooth.h>
|
||||
#import <CoreBluetooth/CoreBluetooth.h>
|
||||
|
||||
#import "internal/platform/implementation/ios/Mediums/GNCMConnection.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, GNCMCentralState) {
|
||||
GNCMCentralStateStopped,
|
||||
GNCMCentralStateScanning,
|
||||
};
|
||||
|
||||
typedef void (^GNCMBleCharacteristicsHandler)(NSArray<CBCharacteristic *> *characteristics,
|
||||
NSError *error);
|
||||
typedef void (^GNCMBleCharacteristicValueHandler)(CBCharacteristic *characteristic, NSError *error);
|
||||
typedef void (^GNCIntHandler)(int);
|
||||
|
||||
/** This lets a GNCIntHandler call itself. */
|
||||
GNCIntHandler GNCRecursiveIntHandler(void (^block)(GNCIntHandler blockSelf, int i)) {
|
||||
return ^(int i) {
|
||||
return block(GNCRecursiveIntHandler(block), i);
|
||||
};
|
||||
}
|
||||
|
||||
/** This represents a discovered peripheral. */
|
||||
@interface GNCMPeripheralInfo : NSObject
|
||||
@property(nonatomic) CBPeripheral *peripheral;
|
||||
|
||||
/** Called when characteristics are discovered. */
|
||||
@property(nonatomic, nullable) GNCMBleCharacteristicsHandler charsHandler;
|
||||
|
||||
/** Called when a characteristic value is read. */
|
||||
@property(nonatomic, nullable) GNCMBleCharacteristicValueHandler charValueHandler;
|
||||
|
||||
@end
|
||||
|
||||
@implementation GNCMPeripheralInfo
|
||||
|
||||
- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_peripheral = peripheral;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
// There is always exactly one info object per peripheral, so compare by identity. This is needed
|
||||
// for maintenance of the peripherals stored in multiple maps.
|
||||
return self == object;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return (NSUInteger)self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface GNCMBleCentral () <CBCentralManagerDelegate, CBPeripheralDelegate>
|
||||
@end
|
||||
|
||||
@@ -32,22 +83,36 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
CBCentralManager *_centralManager;
|
||||
/** Serial background queue for |centralManager|. */
|
||||
dispatch_queue_t _selfQueue;
|
||||
/** Central state for stop or scanning. */
|
||||
GNCMCentralState _state;
|
||||
/** The dictionary keyed by CBPeripheral identifier to value GNCMPeripheralInfo. */
|
||||
NSMutableDictionary<NSUUID *, GNCMPeripheralInfo *> *_nearbyPeripheralsByID;
|
||||
/** Array of characteristic UUID used for discovering. */
|
||||
NSArray<CBUUID *> *_characteristicUUIDs;
|
||||
/** GATT connection result handler. */
|
||||
GNCMGATTConnectionResultHandler _gattConnectionResultHandler;
|
||||
/** GATT service and characteristic discovery result hanadler. */
|
||||
GNCMGATTDiscoverResultHandler _gattDiscoverResultHandler;
|
||||
/** The discovered characteristic values map used to callback for `_gattDiscoverResultHandler`. */
|
||||
NSMutableDictionary<CBUUID *, NSData *> *_gattCharacteristicValues;
|
||||
}
|
||||
|
||||
- (instancetype)initWithServiceUUID:(NSString *)serviceUUID
|
||||
scanResultHandler:(GNCMScanResultHandler)scanResultHandler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_serviceUUID = [CBUUID UUIDWithString:serviceUUID];
|
||||
_scanResultHandler = scanResultHandler;
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
// To make this class thread-safe, use a serial queue for all state changes, and have Core
|
||||
// Bluetooth also use this queue.
|
||||
_selfQueue = dispatch_queue_create("GNCCentralManagerQueue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_nearbyPeripheralsByID = [NSMutableDictionary dictionary];
|
||||
_gattCharacteristicValues = [NSMutableDictionary dictionary];
|
||||
|
||||
// Set up the central manager for scanning.
|
||||
_centralManager = [[CBCentralManager alloc]
|
||||
initWithDelegate:self
|
||||
queue:_selfQueue
|
||||
options:@{CBCentralManagerOptionShowPowerAlertKey : @NO}];
|
||||
|
||||
_state = GNCMCentralStateStopped;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -57,20 +122,248 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// dispatch_sync. This means dealloc must be called from an external queue, which means |self|
|
||||
// must never be captured by any escaping block used in this class.
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
[_centralManager stopScan];
|
||||
[self stopScanningInternal];
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)startScanningWithServiceUUID:(NSString *)serviceUUID
|
||||
scanResultHandler:(GNCMScanResultHandler)scanResultHandler {
|
||||
_serviceUUID = [CBUUID UUIDWithString:serviceUUID];
|
||||
_scanResultHandler = scanResultHandler;
|
||||
_state = GNCMCentralStateScanning;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)connectGattServerWithPeripheralID:(NSString *)peripheralID
|
||||
gattConnectionResultHandler:
|
||||
(GNCMGATTConnectionResultHandler)gattConnectionResultHandler {
|
||||
_gattConnectionResultHandler = gattConnectionResultHandler;
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
GNCMPeripheralInfo *peripheralInfo =
|
||||
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
|
||||
if (!peripheralInfo) return;
|
||||
[_centralManager connectPeripheral:peripheralInfo.peripheral options:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)discoverGattService:(CBUUID *)serviceUUID
|
||||
gattCharacteristics:(NSArray<CBUUID *> *)characteristicUUIDs
|
||||
peripheralID:(NSString *)peripheralID
|
||||
gattDiscoverResultHandler:(GNCMGATTDiscoverResultHandler)gattDiscoverResultHandler {
|
||||
_gattDiscoverResultHandler = gattDiscoverResultHandler;
|
||||
[_gattCharacteristicValues removeAllObjects];
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
GNCMPeripheralInfo *peripheralInfo =
|
||||
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
|
||||
if (!peripheralInfo) return;
|
||||
_characteristicUUIDs = [characteristicUUIDs copy];
|
||||
|
||||
// Start to discover service and the delegate will get its characteristics and read their values
|
||||
// recursively
|
||||
[peripheralInfo.peripheral discoverServices:@[ serviceUUID ]];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)disconnectGattServiceWithPeripheralID:(NSString *)peripheralID {
|
||||
dispatch_sync(_selfQueue, ^{
|
||||
GNCMPeripheralInfo *peripheralInfo =
|
||||
_nearbyPeripheralsByID[[[NSUUID alloc] initWithUUIDString:peripheralID]];
|
||||
if (!peripheralInfo) return;
|
||||
_gattConnectionResultHandler = nil;
|
||||
_gattDiscoverResultHandler = nil;
|
||||
[_centralManager cancelPeripheralConnection:peripheralInfo.peripheral];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark CBCentralManagerDelegate
|
||||
|
||||
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
|
||||
if (central.state == CBManagerStatePoweredOn) {
|
||||
if (central.state == CBManagerStatePoweredOn && !central.isScanning &&
|
||||
_state == GNCMCentralStateScanning) {
|
||||
NSLog(@"[NEARBY] CBCentralManager powered on; starting scan");
|
||||
[self startScanningInternal];
|
||||
} else {
|
||||
NSLog(@"[NEARBY] CBCentralManager not powered on; stopping scan");
|
||||
[self stopScanningInternal];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didDiscoverPeripheral:(CBPeripheral *)peripheral
|
||||
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
|
||||
RSSI:(NSNumber *)RSSI {
|
||||
NSNumber *connectable = advertisementData[CBAdvertisementDataIsConnectable];
|
||||
if (![connectable boolValue]) return;
|
||||
|
||||
// Look for the NC advertisement header in either the service data (from non-iOS) or the
|
||||
// advertised name (from iOS).
|
||||
NSData *serviceData = advertisementData[CBAdvertisementDataServiceDataKey][_serviceUUID]
|
||||
?: advertisementData[CBAdvertisementDataLocalNameKey];
|
||||
|
||||
// Try to look up the peripheral by ID.
|
||||
GNCMPeripheralInfo *info = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!info) {
|
||||
NSLog(@"[NEARBY] New peripheral: %@", peripheral);
|
||||
// This is a new peripheral, so create a new peripheral info object.
|
||||
info = [[GNCMPeripheralInfo alloc] initWithPeripheral:peripheral];
|
||||
} else {
|
||||
info.peripheral = peripheral;
|
||||
}
|
||||
|
||||
_nearbyPeripheralsByID[peripheral.identifier] = info;
|
||||
_scanResultHandler(peripheral.identifier.UUIDString, serviceData);
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
|
||||
NSLog(@"[NEARBY] Connected to peripheral: %@", peripheral);
|
||||
peripheral.delegate = self;
|
||||
// Tell the caller the connection is done.
|
||||
_gattConnectionResultHandler(nil);
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didFailToConnectPeripheral:(CBPeripheral *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Failed to connect to peripheral: %@, error: %@", peripheral, error);
|
||||
// Tell the caller the connection failed.
|
||||
_gattConnectionResultHandler(error);
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central
|
||||
didDisconnectPeripheral:(CBPeripheral *)peripheral
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Disconnected to peripheral: %@, error: %@", peripheral, error);
|
||||
}
|
||||
|
||||
#pragma mark CBPeripheralDelegate
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error {
|
||||
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!peripheralInfo) return;
|
||||
if (error || (peripheral.services.count == 0)) {
|
||||
NSLog(@"[NEARBY] Error reading advertisement: unable to discover services.");
|
||||
_gattDiscoverResultHandler(nil);
|
||||
} else {
|
||||
NSLog(@"[NEARBY] Discovered services for %@: %@", peripheral.name, peripheral.services);
|
||||
|
||||
// Helper functions for discovering characteristics and reading their values.
|
||||
void (^discoverChars)(CBService *, GNCMBleCharacteristicsHandler) =
|
||||
^(CBService *service, GNCMBleCharacteristicsHandler handler) {
|
||||
NSAssert(!peripheralInfo.charsHandler, @"Unexpected characteristic handler");
|
||||
peripheralInfo.charsHandler = handler;
|
||||
|
||||
// Discover all characteristics that may contain the advertisement.
|
||||
[peripheral discoverCharacteristics:_characteristicUUIDs forService:service];
|
||||
};
|
||||
void (^readCharValue)(CBCharacteristic *, GNCMBleCharacteristicValueHandler) =
|
||||
^(CBCharacteristic *characteristic, GNCMBleCharacteristicValueHandler handler) {
|
||||
NSAssert(!peripheralInfo.charValueHandler, @"Unexpected characteristic value handler");
|
||||
peripheralInfo.charValueHandler = handler;
|
||||
[peripheral readValueForCharacteristic:characteristic];
|
||||
};
|
||||
|
||||
// Multiple services may have the same UUID, so find the right service by searching for the
|
||||
// characteristic containing the advertisement with a matching service ID hash.
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
void (^tryService)(int) = GNCRecursiveIntHandler(^(GNCIntHandler tryService, int serviceIndex) {
|
||||
__strong __typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf) return;
|
||||
|
||||
// We've tried all the services, report the discovered characteristics and their values.
|
||||
if (serviceIndex == peripheral.services.count) {
|
||||
NSLog(@"[NEARBY] Done traversing all services or no services to traverse.");
|
||||
_gattDiscoverResultHandler(_gattCharacteristicValues);
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[NEARBY] Trying service: %@", peripheral.services[serviceIndex]);
|
||||
discoverChars(
|
||||
peripheral.services[serviceIndex], ^(NSArray<CBCharacteristic *> *chars, NSError *error) {
|
||||
void (^tryNextService)() = ^{
|
||||
tryService(serviceIndex + 1);
|
||||
};
|
||||
|
||||
// If there was an error or there are no characteristics on this service, try next one.
|
||||
if (error || (chars.count == 0)) {
|
||||
tryNextService();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read each characteristic.
|
||||
void (^tryChar)(int) = GNCRecursiveIntHandler(^(GNCIntHandler tryChar, int charIndex) {
|
||||
// We've tried all characteristics on this service without error, try next service.
|
||||
if (charIndex == chars.count) {
|
||||
NSLog(@"[NEARBY] No matching advertisement found");
|
||||
tryNextService();
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[NEARBY] Trying characteristic: %@", chars[charIndex]);
|
||||
readCharValue(chars[charIndex], ^(CBCharacteristic *characteristic, NSError *error) {
|
||||
if (error) {
|
||||
tryNextService();
|
||||
} else {
|
||||
// We've found the characteristic and its non-nil value. Store it.
|
||||
if (characteristic.value.length != 0) {
|
||||
[_gattCharacteristicValues setObject:characteristic.value
|
||||
forKey:characteristic.UUID];
|
||||
}
|
||||
tryChar(charIndex + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Start searching the characteristics for the current service.
|
||||
tryChar(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Start searching the services.
|
||||
tryService(0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral
|
||||
didDiscoverCharacteristicsForService:(CBService *)service
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Discovered characteristics: %@ error: %@", service.characteristics, error);
|
||||
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!peripheralInfo) return;
|
||||
GNCMBleCharacteristicsHandler charsHandler = peripheralInfo.charsHandler;
|
||||
peripheralInfo.charsHandler = nil;
|
||||
charsHandler(service.characteristics, error);
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral
|
||||
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
|
||||
error:(nullable NSError *)error {
|
||||
NSLog(@"[NEARBY] Read characteristic value: %@ error: %@", characteristic, error);
|
||||
GNCMPeripheralInfo *peripheralInfo = _nearbyPeripheralsByID[peripheral.identifier];
|
||||
if (!peripheralInfo) return;
|
||||
GNCMBleCharacteristicValueHandler valueHandler = peripheralInfo.charValueHandler;
|
||||
peripheralInfo.charValueHandler = nil;
|
||||
valueHandler(characteristic, error);
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
/** Signals the central manager to start scanning. Must be called on _selfQueue */
|
||||
- (void)startScanningInternal {
|
||||
if (![_centralManager isScanning]) {
|
||||
NSLog(@"[NEARBY] startScanningInternal");
|
||||
|
||||
[_centralManager
|
||||
scanForPeripheralsWithServices:@[ _serviceUUID ]
|
||||
options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES}];
|
||||
} else {
|
||||
NSLog(@"[NEARBY] CBCentralManager not powered on; stopping scan");
|
||||
}
|
||||
}
|
||||
|
||||
/** Signals the central manager to stop scanning. Must be called on _selfQueue */
|
||||
- (void)stopScanningInternal {
|
||||
if ([_centralManager isScanning]) {
|
||||
NSLog(@"[NEARBY] stopScanningInternal");
|
||||
_state = GNCMCentralStateStopped;
|
||||
|
||||
[_centralManager stopScan];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#include "internal/platform/implementation/ios/bluetooth_adapter.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -30,6 +31,7 @@ using ::location::nearby::api::ble_v2::BleAdvertisementData;
|
||||
using ::location::nearby::api::ble_v2::BleMedium;
|
||||
using ::location::nearby::api::ble_v2::GattCharacteristic;
|
||||
using ::location::nearby::api::ble_v2::TxPowerLevel;
|
||||
using IOSBluetoothAdapter = ::location::nearby::ios::BluetoothAdapter;
|
||||
|
||||
static const char *const kAdvertisementString = "\x0a\x0b\x0c\x0d";
|
||||
static const TxPowerLevel kTxPowerLevel = TxPowerLevel::kHigh;
|
||||
@@ -37,6 +39,7 @@ static const TxPowerLevel kTxPowerLevel = TxPowerLevel::kHigh;
|
||||
@interface GNCBleTest : XCTestCase
|
||||
@end
|
||||
|
||||
// TODO(b/222392304): More tests on GNCBleTest.
|
||||
@implementation GNCBleTest {
|
||||
std::unique_ptr<BluetoothAdapter> _adapter;
|
||||
std::unique_ptr<BleMedium> _ble;
|
||||
@@ -83,10 +86,8 @@ static const TxPowerLevel kTxPowerLevel = TxPowerLevel::kHigh;
|
||||
// Test creating characteristic.
|
||||
Uuid service_uuid(1234, 5678);
|
||||
Uuid characteristic_uuid(5678, 1234);
|
||||
std::vector<GattCharacteristic::Permission> permissions = {
|
||||
GattCharacteristic::Permission::kRead};
|
||||
std::vector<GattCharacteristic::Property> properties = {
|
||||
GattCharacteristic::Property::kRead};
|
||||
std::vector<GattCharacteristic::Permission> permissions = {GattCharacteristic::Permission::kRead};
|
||||
std::vector<GattCharacteristic::Property> properties = {GattCharacteristic::Property::kRead};
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<GattCharacteristic> gatt_characteristic =
|
||||
@@ -95,10 +96,16 @@ static const TxPowerLevel kTxPowerLevel = TxPowerLevel::kHigh;
|
||||
|
||||
// Test updating characteristic.
|
||||
ByteArray any_byte("any");
|
||||
XCTAssertTrue(
|
||||
gatt_server->UpdateCharacteristic(gatt_characteristic.value(), any_byte));
|
||||
XCTAssertTrue(gatt_server->UpdateCharacteristic(gatt_characteristic.value(), any_byte));
|
||||
|
||||
gatt_server->Stop();
|
||||
}
|
||||
|
||||
- (void)testCreateGattClient {
|
||||
IOSBluetoothAdapter *adapter = static_cast<IOSBluetoothAdapter *>(_adapter.get());
|
||||
auto gatt_client = _ble->ConnectToGattServer(adapter->GetPeripheral(), kTxPowerLevel, {});
|
||||
|
||||
XCTAssert(gatt_client != nullptr);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -77,10 +77,38 @@ class BleMedium : public api::ble_v2::BleMedium {
|
||||
GNCMBlePeripheral* peripheral_;
|
||||
};
|
||||
|
||||
// A concrete implemenation for GattClient.
|
||||
class GattClient : public api::ble_v2::GattClient {
|
||||
public:
|
||||
GattClient() = default;
|
||||
explicit GattClient(GNCMBleCentral* central, const std::string& peripheral_id)
|
||||
: central_(central), peripheral_id_(peripheral_id) {}
|
||||
|
||||
bool DiscoverServiceAndCharacteristics(const Uuid& service_uuid,
|
||||
const std::vector<Uuid>& characteristic_uuids) override;
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<api::ble_v2::GattCharacteristic> GetCharacteristic(
|
||||
const Uuid& service_uuid, const Uuid& characteristic_uuid) override;
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<ByteArray> ReadCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic& characteristic) override;
|
||||
|
||||
bool WriteCharacteristic(const api::ble_v2::GattCharacteristic& characteristic,
|
||||
const ByteArray& value) override;
|
||||
|
||||
void Disconnect() override;
|
||||
|
||||
private:
|
||||
GNCMBleCentral* central_;
|
||||
std::string peripheral_id_;
|
||||
absl::flat_hash_map<api::ble_v2::GattCharacteristic, ByteArray> gatt_characteristic_values_;
|
||||
};
|
||||
|
||||
BluetoothAdapter* adapter_;
|
||||
GNCMBlePeripheral* peripheral_;
|
||||
GNCMBleCentral* central_;
|
||||
dispatch_queue_t callback_queue_;
|
||||
};
|
||||
|
||||
} // namespace ios
|
||||
|
||||
@@ -104,11 +104,19 @@ bool BleMedium::StopAdvertising() {
|
||||
|
||||
bool BleMedium::StartScanning(const Uuid& service_uuid, TxPowerLevel tx_power_level,
|
||||
ScanCallback scan_callback) {
|
||||
central_ = [[GNCMBleCentral alloc]
|
||||
initWithServiceUUID:ObjCStringFromCppString(service_uuid.Get16BitAsString())
|
||||
scanResultHandler:^(NSString* serviceUUID, NSData* serviceData){
|
||||
// TODO(b/228751356): Add scan callback implementation.
|
||||
}];
|
||||
if (!central_) {
|
||||
central_ = [[GNCMBleCentral alloc] init];
|
||||
}
|
||||
|
||||
[central_ startScanningWithServiceUUID:ObjCStringFromCppString(service_uuid.Get16BitAsString())
|
||||
scanResultHandler:^(NSString* peripheralID, NSData* serviceData) {
|
||||
BleAdvertisementData advertisement_data;
|
||||
advertisement_data.service_data = {
|
||||
{service_uuid, ByteArrayFromNSData(serviceData)}};
|
||||
BlePeripheral& peripheral = adapter_->GetPeripheral();
|
||||
peripheral.SetPeripheralId(CppStringFromObjCString(peripheralID));
|
||||
scan_callback.advertisement_found_cb(peripheral, advertisement_data);
|
||||
}];
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -129,7 +137,21 @@ std::unique_ptr<api::ble_v2::GattServer> BleMedium::StartGattServer(
|
||||
std::unique_ptr<api::ble_v2::GattClient> BleMedium::ConnectToGattServer(
|
||||
api::ble_v2::BlePeripheral& peripheral, TxPowerLevel tx_power_level,
|
||||
api::ble_v2::ClientGattConnectionCallback callback) {
|
||||
return nullptr;
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSError* connectedError;
|
||||
BlePeripheral iosPeripheral = static_cast<BlePeripheral&>(peripheral);
|
||||
std::string peripheral_id = iosPeripheral.GetPeripheralId();
|
||||
[central_ connectGattServerWithPeripheralID:ObjCStringFromCppString(peripheral_id)
|
||||
gattConnectionResultHandler:^(NSError* _Nullable error) {
|
||||
connectedError = error;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
|
||||
|
||||
if (connectedError) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<GattClient>(central_, peripheral_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<api::ble_v2::BleServerSocket> BleMedium::OpenServerSocket(
|
||||
@@ -178,8 +200,84 @@ bool BleMedium::GattServer::UpdateCharacteristic(
|
||||
return true;
|
||||
}
|
||||
|
||||
void BleMedium::GattServer::Stop() {
|
||||
[peripheral_ stopGATTService];
|
||||
void BleMedium::GattServer::Stop() { [peripheral_ stopGATTService]; }
|
||||
|
||||
bool BleMedium::GattClient::DiscoverServiceAndCharacteristics(
|
||||
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
|
||||
// Discover all characteristics that may contain the advertisement.
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
gatt_characteristic_values_.clear();
|
||||
CBUUID* serviceUUID = [CBUUID UUIDWithString:ObjCStringFromCppString(std::string(service_uuid))];
|
||||
|
||||
absl::flat_hash_map<std::string, Uuid> gatt_characteristics;
|
||||
NSMutableArray<CBUUID*>* characteristicUUIDs =
|
||||
[NSMutableArray arrayWithCapacity:characteristic_uuids.size()];
|
||||
for (const auto& characteristic_uuid : characteristic_uuids) {
|
||||
[characteristicUUIDs addObject:[CBUUID UUIDWithString:ObjCStringFromCppString(
|
||||
std::string(characteristic_uuid))]];
|
||||
gatt_characteristics.insert({std::string(characteristic_uuid), characteristic_uuid});
|
||||
}
|
||||
|
||||
[central_ discoverGattService:serviceUUID
|
||||
gattCharacteristics:characteristicUUIDs
|
||||
peripheralID:ObjCStringFromCppString(peripheral_id_)
|
||||
gattDiscoverResultHandler:^(NSDictionary<CBUUID*, NSData*>* _Nullable characteristicValues) {
|
||||
if (characteristicValues != nil) {
|
||||
for (CBUUID* charUuid in characteristicValues) {
|
||||
Uuid characteristic_uuid;
|
||||
auto const& it =
|
||||
gatt_characteristics.find(CppStringFromObjCString(charUuid.UUIDString));
|
||||
if (it == gatt_characteristics.end()) continue;
|
||||
|
||||
api::ble_v2::GattCharacteristic characteristic = {.uuid = it->second,
|
||||
.service_uuid = service_uuid};
|
||||
gatt_characteristic_values_.insert(
|
||||
{characteristic,
|
||||
ByteArrayFromNSData([characteristicValues objectForKey:charUuid])});
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
|
||||
|
||||
if (gatt_characteristic_values_.empty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<api::ble_v2::GattCharacteristic> BleMedium::GattClient::GetCharacteristic(
|
||||
const Uuid& service_uuid, const Uuid& characteristic_uuid) {
|
||||
api::ble_v2::GattCharacteristic characteristic = {.uuid = characteristic_uuid,
|
||||
.service_uuid = service_uuid};
|
||||
auto const it = gatt_characteristic_values_.find(characteristic);
|
||||
if (it == gatt_characteristic_values_.end()) {
|
||||
return absl::nullopt; // NOLINT
|
||||
}
|
||||
return it->first;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
absl::optional<ByteArray> BleMedium::GattClient::ReadCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic& characteristic) {
|
||||
auto const it = gatt_characteristic_values_.find(characteristic);
|
||||
if (it == gatt_characteristic_values_.end()) {
|
||||
return absl::nullopt; // NOLINT
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool BleMedium::GattClient::WriteCharacteristic(
|
||||
const api::ble_v2::GattCharacteristic& characteristic, const ByteArray& value) {
|
||||
// No op.
|
||||
return false;
|
||||
}
|
||||
|
||||
void BleMedium::GattClient::Disconnect() {
|
||||
[central_ disconnectGattServiceWithPeripheralID:ObjCStringFromCppString(peripheral_id_)];
|
||||
}
|
||||
|
||||
} // namespace ios
|
||||
|
||||
@@ -31,6 +31,11 @@ class BlePeripheral : public api::ble_v2::BlePeripheral {
|
||||
public:
|
||||
std::string GetAddress() const override;
|
||||
|
||||
std::string GetPeripheralId() const { return peripheral_id_; }
|
||||
void SetPeripheralId(const std::string& peripheral_id) {
|
||||
peripheral_id_ = peripheral_id;
|
||||
}
|
||||
|
||||
private:
|
||||
// Only BluetoothAdapter may instantiate BlePeripheral.
|
||||
friend class BluetoothAdapter;
|
||||
@@ -38,6 +43,7 @@ class BlePeripheral : public api::ble_v2::BlePeripheral {
|
||||
explicit BlePeripheral(BluetoothAdapter* adapter) : adapter_(*adapter) {}
|
||||
|
||||
BluetoothAdapter& adapter_;
|
||||
std::string peripheral_id_;
|
||||
};
|
||||
|
||||
// Concrete BluetoothAdapter implementation.
|
||||
|
||||
Reference in New Issue
Block a user