[BLE Refactor] Implement iOS Scanning modules.

PiperOrigin-RevId: 455274324
This commit is contained in:
edwinwu
2022-06-15 19:23:55 -07:00
committed by Copybara-Service
parent cf85146096
commit 9a1ada9a56
7 changed files with 512 additions and 38 deletions
+2 -3
View File
@@ -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
+29 -1
View File
@@ -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
+106 -8
View File
@@ -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.