iOS 上的藍牙框架 - Core Bluetooth for iOS


原文: Core Bluetooth for iOS 6

Core Bluetooth 是在iOS5首次引入的,它允許iOS設備可以使用健康,運動,安全,自動化,娛樂,附近等外設數據。在iOS 6 中,這個API被擴展了,讓iOS也能成為數據提供方,也就是Server(Peripheral)端,可能使它與其它 iOS 設備交互數據。

Core Bluetooth API 基於BLE4.0規范。這個框架涵蓋了BLE標准的所有細節. 不過,僅僅只有新的iOS設備和MAC是兼容BLE標准的: iPhone 4S, iPhone5, Mac Mini, New iPad, MacBook Air, MacBook Pro. 並且 iOS 6 iPhone 模擬器也支持一樣的標准.這對你在沒有真機時,開發APP時是非常實用的。

相關的類

在CoreBluetooth框架中,有兩個主要的角色:外設和中心(Peripheral and Central) ,整個框架都是圍繞這兩個主要角色設計的,它們之間有一系列的回調交換數據。 下圖1展示了外設和中心(Peripheral and Central)的關系。

Fig1

外設創建或提供一些數據,中心使用這些設備提供的數據。在iOS6之后,iOS 設備也可以即是外設,也可以是中心,但不能在同時間扮演兩個角色。

這兩個組件在CoreBluetooth框架中是分別用兩個類來表示的,中央是CBCentralManager類,外設是CBPeripheralManager類。

在中心,一個 CBPeripheral 對象表示正在連接中的外設,同樣在外設里,一個 CBCentral 表示正在連接中的中心.

你可以理解外設是一個廣播數據的設備,它開始告訴外面的世界說它這兒有一些數據,並且能提供一些服務。另一邊中心開始掃描外面有沒有 自己所需要的服務,如果發現后,會和外設做連接請求,一旦連接確定后,兩個設備就可以傳輸數據了。

除了中心與外設,我們還得考慮他們用於交互的數據結構,這些數據在Services(服務)中被結構化,每個服務由不同的Characteristics(特性)所組成。特性定義為一種屬性類型,並且對應一個邏輯值(比如0x2A49)。

你可以在developer bluetooth這里找到標准服務與特性的列表。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#Services: SpecificationName: Blood Pressure SpecificationType: org.bluetooth.service.blood_pressure AssignedNumber: 0x1810 SpecificationLevel: Adopted #Characteristics: SpecificationName: Blood Pressure Feature SpecificationType: org.bluetooth.characteristic.blood_pressure_feature AssignedNumber: 0x2A49 SpecificationLevel: Adopted 

在中心里,服務由 CBService 類表示,每個服務由代表特性的 CBCharacteristic類所構成。

同樣,在外設中服務與特性由 CBMutableService 與 CBMutableCharacteristicclass 類表示。

下圖解釋了他們之間的關系:

Fig2

CBUUID 和 CBATTRequest 是兩個蘋果提供給我們的幫助類,以便於開發者更簡單地操作數據,稍后你將看到如何使用它們。

使用

不幸的是,Apple提供的文檔目前還不完整,你只有通過WWDC上兩個關於 Core Bluetooth的視頻和頭文件,去理解這個框架是如何工作的。不過,因為我之前已經做過相關方面的事情,我決定和你分享這些內容,我希望下面的教程可以幫助到你。你也可以通過 http://training.invasivecode.com 查看我們的培訓課程.

創建外設 (Peripheral)

為了可以創建一個完整的例子,你需要兩台iOS設備,我將向你展示如何通過藍牙連接這兩個設備,並交換數據。記住先檢查一下你的設備是不是被BLE所支持的。

開始創建一個外設需要下面幾步:

1.創建並且開始Peripheral Manager

2.設置並且發布它的服務。

3.廣播這個服務。

4.和中心連接。

用Single-View Application模板創建一個新的Xcode工程。命名為BlueServer (使用ARC)。工程創建完成后,添加CoreBluetooth.framework 框架。然后打開ViewController.h文件,並且添加以下代碼:

1
#import <CoreBluetooth/CoreBluetooth.h> 

使view controller 遵循 CBPeripheralManagerDelegate 協議,然后添加這個屬性:

1
@property (nonatomic, strong) CBPeripheralManager *manager; 

在ViewController.m中,添加以下代碼到viewDidLoad方法中:

1
self.manager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil]; 

這行代碼初始化了一個 Peripheral Manager (計划中的第一項). 第一個參數是設置delegate(這里的例子就是view controller)。第二參數(隊列)設置為了nil,因為Peripheral Manager 將運行在主線程中。如果你想用同步的線程做更復雜的事情,你需要單獨創建一個隊列並把它放在這個參數中。

一旦Peripheral Manager被初始化后,我們需要及時檢查正在運行的App設備狀態,是不是符合BLE標准的。所以你要實現下面的這個代理方法(如果設備不支持BLE,你可以友好地提醒用戶。你還可以通過拿到的狀態值做更多事情)。

1
2
3
4
5
6
7
8
9
10
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {  switch (peripheral.state) {  case CBPeripheralManagerStatePoweredOn:  [self setupService];  break;  default:  NSLog(@"Peripheral Manager did change state");  break;  } } 

在這里,我檢查了外設的狀態,如果它的狀態是CBPeripheralManagerStatePoweredOn,那這個設備是支持BLE並可以繼續執行。

外設的狀態包括有下面這些

1
2
3
4
5
6
7
8
typedef enum {  CBPeripheralManagerStateUnknown = 0,  CBPeripheralManagerStateResetting,  CBPeripheralManagerStateUnsupported,  CBPeripheralManagerStateUnauthorized,  CBPeripheralManagerStatePoweredOff,  CBPeripheralManagerStatePoweredOn, } CBPeripheralManagerState; 

服務與特性 (Service & Characteristic)

setupService 是一個輔助方法,我們讓它去准備服務和特性,對於這個例子,我們僅僅需要一個服務和一個特性。

每一個服務和特性必要有一個UUID來標識,UUID是一個16位或128位的值。如果你創建的是一個 client-server(中央-外設)應用,那么你需要創建屬於你自己的128位UUID,你必須確保它不能和其他已經存在的服務沖突,如果你要創建一個新的設備,你需要去符合標准委員會的UUID。

如果你創建的是你自己的client-server(正如我們現在做的),我建議你在Terminal下用 uuidgen 命令創建128位的UUID. 所以打開 Terminal並創建兩個(一個為服務,一個為特性).之后,你需要將他們添加到中心和外設應用。這里我們先添加下面幾行在 view controller中。

1
2
static NSString * const kServiceUUID = @"6BC6543C-2398-4E4A-AF28-E4E0BF58D6BC"; static NSString * const kCharacteristicUUID = @"9D69C18C-186C-45EA-A7DA-6ED7500E9C97"; 

注意:這里的UUID每個人生成的都不一樣,最好是你自己生成

這里是 setupService 的實現方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)setupService {  // Creates the characteristic UUID  CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];  // Creates the characteristic  self.customCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];  // Creates the service UUID  CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];  // Creates the service and adds the characteristic to it  self.customService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];  // Sets the characteristics for this service  [self.customService setCharacteristics:@[self.customCharacteristic]];  // Publishes the service  [self.peripheralManager addService:self.customService]; } 

首先,我使用+UUIDWithString:方法創建了一個 UUID 對象,之后我用這個 UUID對象創建了特性。注意,我在初始化時,第三個參數傳的是nil (那個value),之所以這樣做,是因為我告訴 Core Bluetooth我將稍候添加這個特性值,當你需要動態創建數據時,經常這么做。如果你已經有一個靜態的值,你可以直接傳它。

在這個方法中,第一個參數是先創建好的UUID,第二個參數(那個 properties)確定你將如何使用這個特性值,下面是這些可能的值:

1
2
3
4
5
6
7
8
9
10
CBCharacteristicPropertyBroadcast: 允許一個廣播特性值,用於描述特性配置,不允許本地特性 CBCharacteristicPropertyRead: 允許讀一個特性值 CBCharacteristicPropertyWriteWithoutResponse: 允許寫一個特性值,沒有反饋 CBCharacteristicPropertyWrite: 允許寫一個特性值 CBCharacteristicPropertyNotify: 允許通知一個特性值,沒有反饋 CBCharacteristicPropertyIndicate: 允許標識一個特性值 CBCharacteristicPropertyAuthenticatedSignedWrites: 允許簽名一個可寫的特性值 CBCharacteristicPropertyExtendedProperties: 如果設置后,附加特性屬性為一個擴展的屬性說明,不允許本地特性 CBCharacteristicPropertyNotifyEncryptionRequired: 如果設置后,僅允許信任的設備可以打開通知特性值 CBCharacteristicPropertyIndicateEncryptionRequired: 如果設置后,僅允許信任的設備可以打開標識特性值 

最后一個參數是屬性的讀、寫、加密權限,有以下幾種:

1
2
3
4
CBAttributePermissionsReadable CBAttributePermissionsWriteable CBAttributePermissionsReadEncryptionRequired CBAttributePermissionsWriteEncryptionRequired 

創建特性后,我同樣通過+UUIDWithString:方法創建 UUID,然后通過它創建了服務。 最后我為服務設置對應的這個特性。記住,每個服務可以包括多個特性,正如下面的圖3

Fig3

所以我們需要通過一個特性數組來添加到服務中,在這個例子里,這個數組對象只有一個特性。

最后一行的代碼是將服務添加到 Peripheral Manager中,用於發布這個服務。一旦這樣做之后,Peripheral Manager 將會通知它的 delegate調用peripheralManager:didAddService:error:方法。這里如果沒有錯誤,你可以開始廣播這個服務。

1
2
3
4
5
6
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {  if (error == nil) {  // Starts advertising the service  [self.peripheralManager startAdvertising:@{ CBAdvertisementDataLocalNameKey : @"ICServer", CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:kServiceUUID]] }];  } } 

當Peripheral Manager開始廣播這個服務時,delegate 會接收到 peripheralManagerDidStartAdvertising:error: 消息。當中心訂閱 了這個服務時,它的delegate會收到 peripheralManager:central:didSubscribeToCharacteristic:消息,這兒你可以生成動態數據給中心。

現在,發送數據給中心你需要預先寫一些數據的代碼,然后發送updateValue:forCharacteristic:onSubscribedCentrals:到外設。

創建一個中心 (Central)

現在,我們已經有了一個外設,讓我們創建中心(client)。記住,中心是用來處理外設提供的數據的。如上面的圖2所示,這里的中心被 CBCentralManager 對象表示。

讓我們創建一個名字為 BlueClient 的 Xcode 項目,使用ARC,並添加 CoreBluetooth.framework ,在 view controller 頭添加

1
#import <CoreBluetooth/CoreBluetooth.h> 

在中心,你必須遵循兩個協議: CBCentralManagerDelegate 和 CBPeripheralDelegate

1
@interface ViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate> 

並添加兩個屬性:

1
2
@property (nonatomic, strong) CBCentralManager *manager; @property (nonatomic, strong) NSMutableData *data; 

現在正如我們之前為外設創建的做法一樣,我們創建中心對象:

1
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; 

同樣,這里的第一個參數表示 CBCentralManager delegate (這里是 view controller). 第二個參數和之前一樣也表示調度隊列,如果設置為空,他會使用主隊列。

一旦 Central Manager 初始化后,我們同樣也要檢查它的狀態,是不是被 BLE 所支持的APP,實現下面的delegate 方法:

1
2
3
4
5
6
7
8
9
10
11
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {  switch (central.state) {  case CBCentralManagerStatePoweredOn:  // Scans for any peripheral  [self.manager scanForPeripheralsWithServices:@[ [CBUUID UUIDWithString:kServiceUUID] ] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];  break;  default:  NSLog(@"Central Manager did change state");  break;  } } 

這個scanForPeripheralsWithServices:options: 方法用於告訴 Central Manager 開始查看特別的服務,如果你第一個參數用的是nil,這個Central Manager 開始查看所有服務。

這個 kServiceUUID 和創建外設中的 ServiceUUID 一樣。所以我們再次添加下面2行代碼在你的實現類中。

1
2
static NSString * const kServiceUUID = @"6BC6543C-2398-4E4A-AF28-E4E0BF58D6BC"; static NSString * const kCharacteristicUUID = @"9D69C18C-186C-45EA-A7DA-6ED7500E9C97"; 

一旦一個外設在掃描時被發現后,中心 delegate 會收到下面的回調:

1
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI 

這個調用通知Central Manager delegate(在這個例子中就是view controller),一個附帶着廣播數據和信號質量(RSSI-Received Signal Strength Indicator)的周邊被發現。這是一個很酷的參數,知道了信號質量,你可以用它去估計中心與外設的距離。

任何廣播或掃描的響應數據保存在advertisementData 中,可以通過CBAdvertisementData key來訪問它。現在,你可以停止掃描,去連接外設了:

1
2
3
4
5
6
7
8
9
10
11
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {  // Stops scanning for peripheral  [self.manager stopScan];  if (self.peripheral != peripheral) {  self.peripheral = peripheral;  NSLog(@"Connecting to peripheral %@", peripheral);  // Connects to the discovered peripheral  [self.manager connectPeripheral:peripheral options:nil];  } } 

options 參數是一個可選的字典(NSDictionary),如果需要,可以用以下的鍵(keys), 它們的值始終是一個boolean。

1
2
3
CBConnectPeripheralOptionNotifyOnConnectionKey: 這是一個NSNumber(Boolean),表示系統會為獲得的外設顯示一個提示,當成功連接后這個應用被掛起,這對於沒有運行在中心后台模式並不顯示他們自己的提示時是有用的。如果有更多的外設連接后都會發送通知,如果附近的外設運行在前台則會收到這個提示。 CBConnectPeripheralOptionNotifyOnDisconnectionKey: 這是一個NSNumber(Boolean), 表示系統會為獲得的外設顯示一個關閉提示,如果這個時候關閉了連接,這個應用會掛起。 CBConnectPeripheralOptionNotifyOnNotificationKey: 這是一個NSNumber(Boolean),表示系統會為獲得的外設收到通知后顯示一個提示,這個時候應用是被掛起的。 

基於連接的結果,delegate會接收

1
- centralManager:didFailToConnectPeripheral:error: 

或者

1
- centralManager:didConnectPeripheral: 

中的一個。如果成功了,你可以詢問正在廣播服務的那個外設。因此,在didConnectPeripheral 回調中,你可以寫以下代碼:

1
2
3
4
5
6
7
8
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {  // Clears the data that we may already have  [self.data setLength:0];  // Sets the peripheral delegate  [self.peripheral setDelegate:self];  // Asks the peripheral to discover the service  [self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID] ]]; } 

現在,外設開始用一串回調通知它的delegate。在上面一個方法中,我請求外設去尋找服務,外設代理收到 -peripheral:didDiscoverServices: 如果沒有錯誤,外設可以去查找服務所提供特性,你可以這樣做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)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);  // Discovers the characteristics for a given service  if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {  [self.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kCharacteristicUUID]] forService:service];  }  } } 

現在,如果一個特性被發現,外設delegate 又會接收

1
peripheral:didDiscoverCharacteristicsForService:error: 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {  if (error) {  NSLog(@"Error discovering characteristic: %@", [error localizedDescription]);  [self cleanup];  return;  }  if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {  for (CBCharacteristic *characteristic in service.characteristics) {  if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {  [peripheral setNotifyValue:YES forCharacteristic:characteristic];  }  }  } } 

一旦特征的值用setNotifyValue:forCharacteristic: 更新后,外設就會通知它的delegate。

外設的 delegate 就會接收到

1
peripheral:didUpdateNotificationStateForCharacteristic:error: 

這里,你可以用 readValueForCharacteristic: 讀到新的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (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]]) {  return;  }  // 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];  } } 

當外設發送新的值時,外設的 delegate 會收到 peripheral:didUpdateValueForCharacteristic:error:,這個方法的第二個參數包含特性,你可以用 -value 屬性來讀它,這是一個包含了特性值的NSData。

這個時候,你可以為其它數據斷開或等待。

總結

我為你展示了如何使用 Core Bluetooth 框架的基本示例,我希望通過這個教程,加上WWDC視頻,有用的一些文檔能幫助你創建一個 BLE 項目,同時你也可以去參考一些文檔示例,那你會發現我這教程中所有的 delegate 方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM