標籤:
iOS開發藍芽4.0的架構是CoreBluetooth,本文主要介紹CoreBluetooth的使用,關於本文中的程式碼片段大多來自github上的一個demo,地址是myz1104/Bluetooth。
在CoreBluetooth中有兩個主要的部分,Central和Peripheral,有一點類似Client Server。CBPeripheralManager 作為周邊裝置是伺服器。CBCentralManager作為中心裝置是用戶端。所有可用的iOS裝置可以作為周邊(Peripheral)也可以作為中央(Central),但不可以同時既是周邊也是中央。
一般手機是用戶端, 裝置(比如手環)是伺服器,因為是手機去串連手環這個伺服器。周邊(Peripheral)是產生或者儲存了資料的裝置,中央(Central)是使用這些資料的裝置。你可以認為周邊是一個廣播資料的裝置,他廣播到外部世界說他這兒有資料,並且也說明了能提供的服務。另一邊,中央開始掃描附近有沒有服務,如果中央發現了想要的服務,然後中央就會請求串連周邊,一旦串連建立成功,兩個裝置之間就開始交換傳輸資料了。
除了中央和周邊,我們還要考慮他倆交換的資料結構。這些資料在服務中被結構化,每個服務由不同的特徵(Characteristics)組成,特徵是包含一個單一邏輯值的屬性類型。
Peripheral的實現步驟
首先是建立一個周邊
_peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
接下來它就會響應代理的peripheralManagerDidUpdateState方法,可以獲得peripheral的狀態等資訊,
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{ switch (peripheral.state) { case CBPeripheralManagerStatePoweredOn: { [self setupService]; } break; default: { NSLog(@"Peripheral Manager did change state"); } break; }}
當發現周邊裝置的藍芽是可以的時候,這就需要去準備你需要廣播給其他中央裝置的服務和特徵了,這裡通過調用setupService方法來實現。
每一個服務和特徵都需要用一個UUID(unique identifier)去標識,UUID是一個16bit或者128bit的值。如果你要建立你的中央-周邊App,你需要建立你自己的128bit的UUID。你必須要確定你自己的UUID不能和其他已經存在的服務衝突。如果你正要建立一個自己的裝置,需要實現標準委員會需求的UUID;如果你只是建立一個中央-周邊App,我建議你開啟Mac OS X的Terminal.app,用uuidgen命令產生一個128bit的UUID。你應該用該命令兩次,產生兩個UUID,一個是給服務用的,一個是給特徵用的。然後,你需要添加他們到中央和周邊App中。現在,在view controller的實現之前,我們添加以下的代碼:
static NSString * const kServiceUUID = @"1C85D7B7-17FA-4362-82CF-85DD0B76A9A5";static NSString * const kCharacteristicUUID = @"7E887E40-95DE-40D6-9AA0-36EDE2BAE253";
下面就是setupService方法
- (void)setupService{ CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID]; self.customCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable]; CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID]; self.customService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES]; [self.customService setCharacteristics:@[self.customCharacteristic]]; [self.peripheralManager addService:self.customService]; }
當調用了CBPeripheralManager的addService方法後,這裡就會響應 CBPeripheralManagerDelegate的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error方法。這個時候就可以開始廣播我們剛剛建立的服務了。
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{ if (error == nil) { [self.peripheralManager startAdvertising:@{ CBAdvertisementDataLocalNameKey : @"ICServer", CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:kServiceUUID]] }]; }}
當然到這裡,你已經做完了peripheralManager的工作了,中央裝置已經可以接受到你的服務了。不過這是靜止的資料,你還可以調用- (BOOL)updateValue:(NSData *)value forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:(NSArray *)centrals方法可以給中央產生動態資料的地方。
- (void)sendToSubscribers:(NSData *)data { if (self.peripheral.state != CBPeripheralManagerStatePoweredOn) { LXCBLog(@"sendToSubscribers: peripheral not ready for sending state: %d", self.peripheral.state); return; } BOOL success = [self.peripheral updateValue:data forCharacteristic:self.characteristic onSubscribedCentrals:nil]; if (!success) { LXCBLog(@"Failed to send data, buffering data for retry once ready."); self.pendingData = data; return; }}
central訂閱了characteristic的值,當更新值的時候peripheral會調用updateValue: forCharacteristic: onSubscribedCentrals:(NSArray*)centrals去為數組裡面的centrals更新對應characteristic 的值,在更新過後peripheral為每一個central走一遍下面的代理方法
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
peripheral接受到一個讀或者寫的請求時,會響應以下兩個代理方法
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests
那麼現在peripheral就已經建立好了。
建立一個中央
建立中央並且串連周邊
現在,我們已經有了一個周邊,讓我們建立我們的中央。中央就是那個處理周邊發送來的資料的裝置。
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
當Central Manager被初始化,我們要檢查它的狀態,以檢查運行這個App的裝置是不是支援BLE。實現CBCentralManagerDelegate的代理方法:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{ switch (central.state) { case CBCentralManagerStatePoweredOn: { [self.manager scanForPeripheralsWithServices:@[ [CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }]; } break; default: { NSLog(@"Central Manager did change state"); } break; }}
當app的裝置是支援藍芽的時候,需要調用CBCentralManager執行個體的- (void)scanForPeripheralsWithServices:(NSArray *)serviceUUIDs options:(NSDictionary *)options方法,用來尋找一個指定的服務的peripheral。一旦一個周邊在尋找的時候被發現,中央的代理會收到以下回調:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ NSString *UUID = [peripheral.identifier UUIDString]; NSString *UUID1 = CFBridgingRelease(CFUUIDCreateString(NULL, peripheral.UUID)); NSLog(@"----發現外設----%@%@", UUID,UUID1); [self.manager stopScan]; if (self.peripheral != peripheral) { self.peripheral = peripheral; NSLog(@"Connecting to peripheral %@", peripheral); [self.manager connectPeripheral:peripheral options:nil]; }}
這個時候一個附帶著廣播資料和訊號品質(RSSI-Received Signal Strength Indicator)的周邊被發現。這是一個很酷的參數,知道了訊號品質,你可以用它去判斷遠近。任何廣播、掃描的響應資料儲存在 advertisementData 中,可以通過CBAdvertisementData 來訪問它。
這個時候你用可以串連這個周邊裝置了,
[self.manager connectPeripheral:peripheral options:nil];
它會響應下面的代理方法,
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@"----成功串連外設----"); [self.peripheral setDelegate:self]; [self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID]]];}
訪問周邊的服務
上面的CBCentralManagerDelegate代理會返回CBPeripheral執行個體,它的- (void)discoverServices:(NSArray *)serviceUUIDs方法就是訪問周邊的服務了,這個方法會響應CBPeripheralDelegate的方法。
- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error{ NSLog(@"----didDiscoverServices----Error:%@",error); if (error) { NSLog(@"Error discovering service: %@", [error localizedDescription]); [self cleanup]; return; } for (CBService *service in aPeripheral.services) { NSLog(@"Service found with UUID: %@", service.UUID); if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) { [self.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kCharacteristicUUID],[CBUUID UUIDWithString:kWrriteCharacteristicUUID]] forService:service]; } }}
在上面的方法中如果沒有error,可以調用discoverCharacteristics方法請求周邊去尋找它的服務所列出的特徵,它會響應下面的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ if (error) { NSLog(@"Error discovering characteristic: %@", [error localizedDescription]); return; } if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) { for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"----didDiscoverCharacteristicsForService---%@",characteristic); if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) { [peripheral readValueForCharacteristic:characteristic]; [peripheral setNotifyValue:YES forCharacteristic:characteristic]; } if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kWrriteCharacteristicUUID]]) { writeCharacteristic = characteristic; } } }}
這個時候peripheral可以調用兩個方法,
[peripheral readValueForCharacteristic:characteristic]這個是讀特徵值的,會響應- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
[peripheral setNotifyValue:YES forCharacteristic:characteristic];會響應- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ if (error) { NSLog(@"Error changing notification state: %@", error.localizedDescription); } // Exits if it‘s not the transfer characteristic if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]] ) { // Notification has started if (characteristic.isNotifying) { NSLog(@"Notification began on %@", characteristic); [peripheral readValueForCharacteristic:characteristic]; } else { // Notification has stopped // so disconnect from the peripheral NSLog(@"Notification stopped on %@. Disconnecting", characteristic); [self.manager cancelPeripheralConnection:self.peripheral]; } }}- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ NSLog(@"----Value---%@",characteristic.value); if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) { if (writeCharacteristic) { Byte ACkValue[3] = {0}; ACkValue[0] = 0xe0; ACkValue[1] = 0x00; ACkValue[2] = ACkValue[0] + ACkValue[1]; NSData *data = [NSData dataWithBytes:&ACkValue length:sizeof(ACkValue)]; [self.peripheral writeValue:data forCharacteristic:writeCharacteristic type:CBCharacteristicWriteWithoutResponse]; } }}
在上面的方法中,- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type是一個對周邊裝置寫資料的方法,它會響應下面的方法
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ NSLog(@"---didWriteValueForCharacteristic-----"); if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kWrriteCharacteristicUUID]]) { NSLog(@"----value更新----"); }}
這樣,中央裝置也實現了讀寫資料的功能了。
另外,github上有一個封裝的第三方開源藍芽架構,地址是kickingvegas/YmsCoreBluetooth
iOS藍芽4.0協議簡單介紹