iOS 上的藍牙框架 - Core Bluetooth for iOS [譯]

轉自:doruby's blog

http://doruby.com/blog/2013/08/15/core-bluetooth-for-ios/

所須環境: iOS 6 以上

原文: 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 方法。

http://blog.csdn.net/jimoduwu/article/details/8917104

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章