概述 公司的項目是醫療類的項目,是以這段一直在和藍牙打交道。我使用的是蘋果原生的架構CoreBluetooth。在對接幾個藍牙裝置的過程中,也遇到一些坑,下文我會一一列舉。 git上有個庫BabyBluetooth 基于原生CoreBluetooth架構進行了封裝,使用起來也很友善,大家可以嘗試一下。 那麼我們開始吧!
正文 在了解下文内容之前,我已預設你已經了解一些基本概念: 什麼是中心裝置 什麼是外圍裝置 什麼是服務(service) 什麼是特性(characteristic) 什麼是訂閱(notify) 什麼是UUID ... 基本了解了以上一些概念,下面的内容将比較好了解。
需要注明,下面的UUID是我的藍牙裝置中的Service和Characteristic的UUID,要注意根據自己的藍牙裝置提供的Service和Characteristic的UUID來替換
// 藍牙裝置提供的服務的UUID #define kCGMServiceTwoUUID @"0000FFF0-0000-1000-8000-00805F9B34FB" // 藍牙裝置提供的寫入特性 #define kCGMCharacteristicOneUUID @"0000FFF1-0000-1000-8000-00805F9B34FB" // 藍牙裝置提供的notify特性 #define kCGMCharacteristicTwoUUID @"0000FFF2-0000-1000-8000-00805F9B34FB"
那麼,先讓我們了解下藍牙互動流程中幾個常用的回調。 中心裝置CBCentralManager更新裝置藍牙狀态的回調 - (void)centralManagerDidUpdateState:(CBCentralManager *)central{ switch (central.state) { case CBCentralManagerStatePoweredOn: { // 掃描外圍裝置 [self.centeralManager scanForPeripheralsWithServices:nil options:nil]; } break; default: NSLog(@"裝置藍牙未開啟"); break; } } 中心裝置已經發現外圍裝置回調
這裡有幾個問題值得注意: 1. 在ios中藍牙廣播資訊中通常會包含以下4種類型的資訊。ios的藍牙通信協定中不接受其他類型的廣播資訊。是以需要注意的是,如果需要在掃描裝置時,通過藍牙裝置的Mac位址來唯一辨識裝置,那麼需要與藍牙裝置的硬體工程師溝通好:将所需要的Mac位址放到一下幾種類型的廣播資訊中。通常放到kCBAdvDataManufacturerData這個字段中。kCBAdvDataIsConnectable = 1; kCBAdvDataLocalName = XXXXXX; kCBAdvDataManufacturerData =XXXX; kCBAdvDataTxPowerLevel = 0; 2. 裝置的UUID(peripheral.identifier)是由兩個裝置的mac通過算法得到的,是以不同的手機連接配接相同的裝置,它的UUID都是不同的,無法辨別裝置。 3. 蘋果與藍牙裝置連接配接通信時,使用的并不是蘋果藍牙子產品的Mac位址,使用的是蘋果随機生成的十六進制碼作為手機藍牙的Mac與外圍藍牙裝置進行互動。如果藍牙裝置與手機在一定時間内多次通信,那麼使用的是首次連接配接時随機生成的十六進制碼作為Mac位址,超過這個固定的時間段,手機會清空已随機生成的Mac位址,重新生成。也就是說外圍裝置是不能通過與蘋果手機的互動時所擷取的藍牙Mac位址作為手機的唯一辨別的。(這是在與寫藍牙裝置的固件工程師聯調時根據問題的現象推測的。至于蘋果藍牙通訊協定的底層是否确實完全像我所說的這樣,希望了解的讀者能提供幫助。在此先謝過。)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { NSLog(@"advertisementData.kCBAdvDataManufacturerData = %@", advertisementData[@"kCBAdvDataManufacturerData"]); _connectPeripheral = peripheral; // [self.centeralManager connectPeripheral:peripheral options:nil]; if ([advertisementData[@"kCBAdvDataLocalName"] hasPrefix:@"SN"]){ NSLog(@"已搜尋到裝置"); NSLog(@"peripheral.identifier = %@ peripheral.name = %@", peripheral.identifier, peripheral.name); [_delegate getAdvertisementData:advertisementData andPeripheral:peripheral]; [_peripheralArray addObject:peripheral]; } }
中心裝置裝置連接配接成功回調 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { // 裝置停止掃描 [self.centeralManager stopScan]; peripheral.delegate = self; dispatch_after(2, dispatch_get_main_queue(), ^{ // 查找服務 [_connectPeripheral discoverServices:@[[CBUUID UUIDWithString:kCGMServiceTwoUUID]]]; }); }
中心裝置裝置連接配接失敗回調 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ [_operationDelegate failToConnect]; }
中心裝置裝置連接配接中斷回調 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"連接配接斷開 %@", [error localizedDescription]); [_operationDelegate disconnected]; }
外圍裝置(CBPeripheral)發現服務(service)回調 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { if (error) { // 輸出錯誤資訊 NSLog(@"discoverServices.error============ %@", [error localizedDescription]); return; } // 周遊裝置提供的服務 for (CBService *service in peripheral.services) { NSLog(@"service.UUID = ------------- = %@", service.UUID.UUIDString); // 找到需要的服務,并擷取該服務響應的特性 if([service.UUID isEqual:[CBUUID UUIDWithString:kCGMServiceTwoUUID]]) { [service.peripheral discoverCharacteristics:nil forService:service]; NSLog(@"開始查找cgm的characteristic"); } } }
外圍裝置發現特性(characteristic)回調 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { if (error) { // 輸出錯誤資訊 NSLog(@"discoverCharacteristics.error=========== %@", [error localizedDescription]); return; } // 周遊服務中的所有特性 for (CBCharacteristic *characteristic in service.characteristics) { if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) { // 設定讀寫的特性
_readAndWriteCharacteristic = characteristic; } else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicTwoUUID]]) { // 設定需要訂閱的特性 _notifyCharacteristic = characteristic; [_connectPeripheral setNotifyValue:YES forCharacteristic:_notifyCharacteristic]; } } }
外圍裝置資料更新回調, 可以在此回調方法中讀取資訊(無論是read的回調,還是notify(訂閱)的回調都是此方法) - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ if (error) { // 輸出錯誤資訊 NSLog(@"didupadteValueForCharacteristic error ============ %@", [error localizedDescription]); return; } NSLog(@"value ============= %@", characteristic.value); // 解析資料 NSData *data = characteristic.value; // 将NSData轉Byte數組 NSUInteger len = [data length]; Byte *byteData = (Byte *)malloc(len); memcpy(byteData, [data bytes], len); NSMutableArray *commandArray = [NSMutableArray arrayWithCapacity:0]; // Byte數組轉字元串 for (int i = 0; i < len; i++) { NSString *str = [NSString stringWithFormat:@"%02x", byteData[i]]; [commandArray addObject:str]; NSLog(@"byteData = %@", str); } // 輸出資料 [_operationDelegate dataWithCharacteristic:commandArray]; }
特性已寫入外圍裝置的回調(如果寫入類型為CBCharacteristicWriteWithResponse 回調此方法,如果寫入類型為CBCharacteristicWriteWithoutResponse不回調此方法) - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ if (error) { NSLog(@"write.error=======%@",error.userInfo); } // 讀資料 if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) { [self readCharacter]; } }
外圍裝置訂閱特征值狀态改變成功的回調需要注意的是這裡是對kCGMCharacteristicOneUUID這個特性進行寫入,這裡之是以這樣操作是因為我的藍牙裝置的藍牙協定是這樣定義的,是以這裡不要照抄照搬,要按照你的藍牙裝置的通訊協定來确定,對哪一個特性進行read,對哪個特性進行write,以及對哪個特性進行設定Notify
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { NSLog(@"error = %@", [error localizedDescription]); } // 對特性kCGMCharacteristicTwoUUID設定notify(訂閱),成功以後回 if ([characteristic.UUID.UUIDString isEqualToString:kCGMCharacteristicTwoUUID] && characteristic.isNotifying) { // 寫資料 回調-didWriteValueForCharacteristic NSLog(@"寫資料到cgm裝置的characteristic = %@", _readAndWriteCharacteristic.UUID.UUIDString); [_operationDelegate writeCharacteristic]; } }
另外,除了回調以外,還有幾個點需要注意: 搜尋外圍裝置 - (void)searchlinkDevice{ // 實作代理 // 掃描裝置// _centeralManager = [[CBCentralManager alloc] initWithDelegate:self// queue:nil]; if(self.centeralManager.state == CBCentralManagerStatePoweredOff) { // 藍牙關閉的 } else if(self.centeralManager.state == CBCentralManagerStateUnsupported) { // 裝置不支援藍牙 } else if(self.centeralManager.state == CBCentralManagerStatePoweredOn || self.centeralManager.state == CBCentralManagerStateUnknown) { // 開啟的話開始掃描藍牙裝置 [self.centeralManager scanForPeripheralsWithServices:nil options:nil]; double delayInSeconds = 20.0; // 掃描20s後未掃描到裝置停止掃描 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { [self stopScan]; }); } }
對某個特性(characteristic)寫入資料 - (void)writeCharacter:(NSData *)data{ NSLog(@" characteristic.uuid = %@ data ==== %@", _readAndWriteCharacteristic.UUID.UUIDString, data); if ([_readAndWriteCharacteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) { [_connectPeripheral writeValue:data forCharacteristic:_readAndWriteCharacteristic type:CBCharacteristicWriteWithResponse]; } else { [_connectPeripheral writeValue:data forCharacteristic:_readAndWriteCharacteristic type:CBCharacteristicWriteWithoutResponse]; } }
讀資料 需要注意的是這裡讀取藍牙資訊 (但并不是在傳回值中接收,要在 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;這個回調方法中接收) - (void)readCharacter{ [_connectPeripheral readValueForCharacteristic:_readAndWriteCharacteristic]; }
另外完整的源碼請到這裡https://github.com/xuzhengquan/BlueToothDemo/檢視,demo中提供了藍牙操作的工具類,由于藍牙通訊協定的不同,是以在UI上沒有做更多的工作,隻是提供了搜尋裝置時展示了一下外圍裝置資訊,還請諒解。
連結:http://www.jianshu.com/p/3b404bd672a8