接觸iOS藍牙框架(CoreBluetooth)有一年多的時間了,遇到過很多問題,最近有空就想著記錄幾個自己覺得比較重要的知識點。
- 同一個iphone在搜索時對不同的藍牙設備會生成不同的UUID(universally unique identifier),當更換iphone去連接同一個設備時,需要重新搜索獲得新的UUID才能連接(調用下面的方法,傳入目的設備的UUID,如果得到的數組為空就需要重新搜索設備才能連接)
- (NSArray<CBPeripheral >)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID >)identifiers NS_AVAILABLE(NA, 7_0);
- 不是連接過某個藍牙設備就表示iphone已記住這個設備(retrievePeripheralsWithIdentifiers:返回有效值,下一次可以快速重新連接),只有跟設備有過讀寫交互才會記住這個設備;
you cannot initiate pairing from the iOS central side. Instead, you have to read/write a characteristic value,and then let your peripheral respond with an "Insufficient Authentication" error.iOS will then initiate pairing, will store the keys for later use (bonding) and encrypts the link. As far as I know,it also caches discovery information, so that future connections can be set up faster.
- iOS8.0以上斷開手機藍牙時,會先響應CBCentralManagerDelegate的藍牙連接斷開代理方法,再響應藍牙狀態變更代理方法;iOS8.0以下斷開手機藍牙只響應藍牙狀態變更代理方法;
藍牙連接斷開代理方法:
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
藍牙狀態變更代理方法:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
- 正常情況下需要設置一個定時器去處理連接藍牙設備和讀寫交互長時間無響應的情況(如果在初始化centralManager的時候選擇在主線程做藍牙相關的操作,然后用NSTimer去處理這些無響應的情況,這樣沒有問題),但大多數情況下藍牙的操作都是放在子線程去做的,為了實現多線程環境下timer的正常運行,一開始用的是NSThread+NSTimer的方式,timer正常工作了,但是timer沒被釋放掉,最終換成GCD的timer實現這個功能。
-(void)createConnectTimerWithInterval:(NSTimeInterval)interval timerHandler:(void (^)(void))timerHandler {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC));
dispatch_source_set_timer(_connectTimer, start, (uint64_t)(0.1 * NSEC_PER_SEC), 0);
dispatch_source_set_event_handler(_connectTimer, ^{
timerHandler();
});
dispatch_resume(_connectTimer);
}
-
藍牙的操作步驟都是搜索->連接->獲取services和characteristics,這三個步驟完成之后才能進行讀寫交互;不同的藍牙設備攜帶不同services和characteristics,不同的characteristic也有不同的writeType,所以為了在一定程度上做到一勞永逸,自己寫了個配置文件:
- 配置單個service
@interface WPBluetoothService : NSObject
-(instancetype)initWithServiceUUIDString:(NSString *)serviceUUIDString sendCharacteristicUUIDString:(NSString *)sendUUIDString receiveCharacteristicUUIDString:(NSString *)receiveUUIDString writeType:(CBCharacteristicWriteType)type;
@property (nonatomic, strong) CBUUID *serviceUUID;
@property (nonatomic, strong) CBUUID *sendCharacteristicUUID;
@property (nonatomic, strong) CBUUID *receiveCharacteristicUUID;
@property (nonatomic, strong) CBService *service;
@property (nonatomic, strong) CBCharacteristic *sendCharacteristic;
@property (nonatomic, strong) CBCharacteristic *receiveCharacteristic;
@property (nonatomic, assign) CBCharacteristicWriteType writeType;
-(BOOL)isAvailable;
- 配置整個藍牙設備攜帶的service
@interface WPBluetoothServiceProfile : NSObject
@property(nonatomic, strong) NSArray *bleServices;
@property(nonatomic, assign) BOOL batteryServiceEnable;
@property(nonatomic, assign) BOOL showSystemPowerAlert;
+(WPBluetoothServiceProfile *)sharedProfile;
-(WPBluetoothService *)serviceWithServiceUUID:(CBUUID *)uuid;
-(WPBluetoothService *)serviceWithSendCharacteristicUUID:(CBUUID *)uuid;
-(WPBluetoothService *)serviceWithReceiveCharacteristicUUID:(CBUUID *)uuid;
-(NSArray *)allServiceUUIDs;
-(void)resetAllServices;
-(BOOL)allServicesAvailable;
使用藍牙功能之前,只能配置每個WPBluetoothService對象的CBUUID類型的屬性,在連接成功成功后獲得services和characteristics成功才能填充剩下的屬性值。
-(void)configBluetoothService {
NSMutableArray *services = [[NSMutableArray alloc] init];
[services addObject: [[WPBluetoothService alloc] initWithServiceUUIDString:kCommandServiceId
sendCharacteristicUUIDString:kCommandSendCharacteristicUUIDString
receiveCharacteristicUUIDString:kCommandReceiveCharacteristicUUIDString
writeType:CBCharacteristicWriteWithResponse]];
[WPBluetoothServiceProfile sharedProfile].bleServices = services;
[WPBluetoothServiceProfile sharedProfile].batteryServiceEnable = YES;
[WPBluetoothServiceProfile sharedProfile].showSystemPowerAlert = YES;
}
發現services(僅貼核心代碼,省略一些基礎值的判斷):
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
[[WPBluetoothServiceProfile sharedProfile] resetAllServices];
for (CBService *service in services) {
WPBluetoothService *bleService = [[WPBluetoothServiceProfile sharedProfile] serviceWithServiceUUID:service.UUID];
if (bleService) {
bleService.service = service;
continue;
}
if ([service.UUID isEqual:[CBUUID UUIDWithString:kBatteryServiceId]]) {
batteryServiceId = service;
}
}
for (WPBluetoothService *bleService in [WPBluetoothServiceProfile sharedProfile].bleServices) {
if (bleService.service) {
[peripheral discoverCharacteristics: [NSArray arrayWithObjects:bleService.sendCharacteristicUUID, bleService.receiveCharacteristicUUID, nil]
forService:bleService.service];
}
}
}
發現characteristics(僅貼核心代碼,省略一些基礎值的判斷):
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error {
WPBluetoothService *bleService = [[WPBluetoothServiceProfile sharedProfile] serviceWithServiceUUID:service.UUID];
if(bleService) {
for (CBCharacteristic *characteristic in characteristics) {
if ([characteristic.UUID isEqual:bleService.receiveCharacteristicUUID]) {
bleService.receiveCharacteristic = characteristic;
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
} else if ([characteristic.UUID isEqual:bleService.sendCharacteristicUUID]) {
bleService.sendCharacteristic = characteristic;
}
}
}
}