在iOS開發中,實現藍牙通信有兩種方式,一種是使用傳統的GameKit.framework,另一種就是使用在iOS 5中加入的CoreBluetooth.framework。
利用CoreBluetooth框架,我們可以輕松實現兩個iOS設備、iOS設備與非iOS藍牙設備的交互。要注意的一點是目前這個框架只能支持藍牙4.0BLE標准,所以對硬件上是有一定要求的,iPhone 4S及以後的設備,第三代iPad及以後的設備是支持這一標准的。
術語解釋
我們首先看一下CoreBluetooth框架的結構。這是CoreBluetooth/CoreBluetooth.h文件中的聲明。
#ifndef _CORE_BLUETOOTH_H_ #define _CORE_BLUETOOTH_H_ #endif #import #import #import #import #import #import #import #import #import #import #import
其中,CBCentral開頭的都是中心設備類,CBPeripheral開頭的都是外設類。這就要講到藍牙設備的兩個角色了。
中心設備:中心設備可以理解為是處理數據的iOS設備,比如你的 iPhone、iPad 等。
外設:外設顧名思義,就是產生數據的外部設備。這個外部設備可以是單片機、嵌入式設備甚至是另一個iOS設備等等。外設可以通過其傳感器等產生有用數據,數據後通過藍牙傳給中心設備使用。
在建立連接的之前,外設向外發出廣播數據(advertisementData,官方描述“A dictionary containing any advertisement and scan response data.”),廣播數據是一個字典類數據,中心設備可以獲取一定范圍內的外設發出的廣播數據。
現在開始
初始化
為了使用CoreBluetooth框架中的回調方法,我們要使用CBCentralManagerDelegate、CBPeripheralDelegate這兩個協議。
我們先要初始化一個CBCentralManager類的對象。代碼如下:
@interface SKBluetoothManager : NSObject{ CBCentralManager *manager; id delegate; } manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; manager.delegate = self;
掃描外設
初始化完成,我們就可以讓管理器開始掃描外設了:
[manager scanForPeripheralsWithServices:nil options:nil];
在設備發現周圍外設的advertisementData後,會回調這個方法:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
其中參數central指回調這個方法的中心設備,peripheral指發現的外設對象CBPeripheral,advertisementData就是前面說的字典類型廣播數據,RSSI是當前外設的信號強度,單位是dbm。
剛開始對陌生的藍牙設備調試時,建議我們先用一個數組NSArray將所掃描到的外設進行保存:
[_peripherals addObject:peripheral];
連接外設
保存後,可根據設備的UUID來確定該設備是否為我們需要進行操作的藍牙設備,在確認外設身份後,即可發起對外設的連接操作:
if ([peripheral.identifier.UUIDString isEqualToString:kPeripheralUUID]) { [manager stopScan]; [manager connectPeripheral:peripheral options:nil]; NSLog(@"連接外設:%@",peripheral.description); self.peripheral = peripheral; }
在此步操作後,我們完成了對藍牙設備的掃描工作,接下來的回調方法分為兩種情況:
連接到外設後
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@"已經連接到:%@", peripheral.description); peripheral.delegate = self; [central stopScan]; [peripheral discoverServices:nil]; }
一旦連接好外設,我們就可以馬上停止掃描。然後發起對服務的搜索:
- (void)discoverServices:(NSArray *)serviceUUIDs;
此處參數位需要掃描的服務的UUID的數組。文檔中特別提到,若該參數為nil,將會掃描所有的服務。
連接失敗後
在連接外設失敗的回調方法中,提供了error參數,可根據實際需要來做異常處理,在此不做過多說明
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"連接%@失敗",peripheral); }
在搜索到藍牙設備的服務後,將會回調
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
若有錯誤發生,通過NSError異常處理。
掃描服務
由於服務在peripheral裡是以NSArray的形式存在的,所以我們要對peripheral中的所有服務進行遍歷:
for (CBService *service in peripheral.services) { //發現服務 if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) { NSLog(@"發現服務:%@", service.UUID); [peripheral discoverCharacteristics:nil forService:service]; break; } }
掃描特征值
在遍歷中,趁熱打鐵,直接對其特征值進行掃描,
[peripheral discoverCharacteristics:nil forService:service];
這裡與掃描service是相同的,若掃描所有的特征值,直接傳入nil作為參數即可。
這裡的kServiceUUID是我們根據藍牙設備的具體情況,提前設置好的UUID常量。本文所有kXxxxxUUID均為預設的UUID常量。
同樣的,characteristics也是一個數組,我們利用像遍歷services一樣的方式來遍歷所有的特征值。
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { if (error) { NSLog(@"搜索特征%@時發生錯誤:%@", service.UUID, [error localizedDescription]); return; } NSLog(@"服務:%@",service.UUID); for (CBCharacteristic *characteristic in service.characteristics) { // NSLog(@"特征:%@",characteristic); //發現特征 if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicWriteUUID]]) { _writeCharacteristic = characteristic; } if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicNotifyUUID]]) { NSLog(@"監聽特征:%@",characteristic);//監聽特征 [self.peripheral setNotifyValue:YES forCharacteristic:characteristic]; _isConnected = YES; } } }
特別要提到的是,我們不同的藍牙設備有不同的服務和特征值。我的藍牙模塊的說明文檔中已經說清楚了,write特征、read特征、notify特征,所以在此根據自身需要,來對不同的特征值進行操作。
設置監聽
我在此要解釋一下,當我們試圖去讀取藍牙外設發過來的數據時,一定要找准特征值,用這個方法進行訂閱。每次特征值變化的時候,就會有回調方法執行,從而達到讀取數據的目的。容易出錯誤,一定分清楚到底哪個特征值該被訂閱。
在訂閱了特征值後,我們嘗試用藍牙外設發送一些數據出來,即可回調下一階段的方法:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { NSLog(@"更新特征值%@時發生錯誤:%@", characteristic.UUID, [error localizedDescription]); return; } // 收到數據 [delegate didGetDataForString:[self hexadecimalString:characteristic.value]]; // NSLog(@"%@",[self hexadecimalString:characteristic.value]); }
數據的轉換
我們接收到的數據,正是characteristic.value,這是一個NSData類數據,我們可以通過UTF8StringEncoding來轉化為NSString,為了代碼結構清晰,我專門把NSData和NSString互轉寫成了兩個方法:
//將傳入的NSData類型轉換成NSString並返回 - (NSString*)hexadecimalString:(NSData *)data{ NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; return result; } //將傳入的NSString類型轉換成NSData並返回 - (NSData*)dataWithHexstring:(NSString *)hexstring{ NSData *aData; return aData = [hexstring dataUsingEncoding: NSASCIIStringEncoding]; }
在拿到字符串後,通過各種回調方法,處理UI變動。
結語
整個藍牙開發實現方便,但回調方法非常多,新手容易暈頭轉向。按部就班把每個回調方法實現,即可保證藍牙開發的順利進行。
最後貼上我封裝的 SKBluetooth 的地址:SKBluetooth