iOS BLE 開發小記[3] 如何實現一個 Local Peripheral

歡迎訪問個人博客 muhlenXi,該文章出自個人博客, 歡迎轉載,轉載請註明來源: muhlenxi.com/2017/05/01/…html

導語:

在這一節,你將會學到,如何經過 CoreBluetooth 框架來實現 Local Peripheral 方面的功能和代理方法。數據庫

在 iOS BLE 開發小記[2]中,你已經學到了如何在 Central 方面去調用 BLE 的經常使用方法。在這一節中,你將學習用 CoreBluetooth 框架來調用 Peripheral 方面 BLE 的經常使用方法。經過本文的示例代碼,將會引導你開發一個將你的 Local 設備實現爲 Local Peripheral。你將會從中學到:數組

  • 如何建立一個 Peripheral Manager 對象
  • 如何爲你的 Local Peripheral 設置 Services 和 Characteristics
  • 如何發佈你的 Services 和 Characteristics 數據
  • 如何廣播你的設備
  • 如何對鏈接的 Central 作讀寫請求響應
  • 如何發送更新後的值給訂閱的 Central

或許你發現示例代碼過於簡單和抽象,你須要在你的 App 中作些恰當的練習來掌握這些內容。更高級的技巧和最佳實踐在後續的文章中將會講解。緩存

Peripheral 實現詳情

建立一個 Peripheral Manager 對象

在 Local Device(當前設備)實現 Peripheral 規範的第一步是分配(allocate)和初始化(initialize)一個周邊管理(Peripheral Manager),(用 CBPeripheralManager 對象表示),經過調用 CBPeripheralManager 的 initWithDelegate:queue:options: 方法來建立管理對象,以下所示app

myPeripheralManager =
        [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
複製代碼

在示例代碼中,設置 Delegate 爲 self 是爲了接收 Peripheral 的事件響應,將參數 dispatch queue 置爲 nil。意味着 Peripheral Manager 將會在主隊列中分發事件。框架

當你建立一個 Peripheral Manger 對象時,Peripheral Manager 會經過 peripheralManagerDidUpdateState: 方法來代理回調,你必須實現這個代理方法來確保當前設備是否支持 BLE 技術,關於代理方法的詳情能夠查閱 CBPeripheralManagerDelegate Protocol Reference.學習

設置你的 Services 和 Characteristics

在第一節中,咱們瞭解到,一個 Local Peripheral 採用樹形結構來組織 Services 和 Characteristics 的數據。所以必須採用樹形結構方式來設置 Local Peripheral 的 Services 和 Characteristics。你第一步要作的是搞清和理解 Service 和 Characteristic 是如何標識的。fetch

經過 UUID 標識 Services 和 Characteristics

Peripheral 的 Service 和 Characteristic 是經過 128 位的特定藍牙 UUID(通用惟一識別碼)來標識的,在 CoreBluetooth 中是用 CBUUID 對象來表示的。並非全部的 UUID 都是經過 Bluetooth Special Interest Group (藍牙特別興趣小組)預約義的。爲了方便起見,Bluetooth SIG 定義和發佈了許多通用的 16位 UUID。舉個例子,Bluetooth SIG 事先定義了一個16位的 UUID 用來標識一個心率 Service,該 UUID 是 128位 UUID 0000180D-0000-1000-8000-00805F9B34FB 進行縮減而來的,這是基於藍牙 4.0 規範中,第 3 卷 F 部分第 3.2.1 節定義的藍牙基礎 UUID。ui

CBUUID 提供了一個處理比較長的 UUID 的工廠方法,舉個例子,生成一個表示心率 Service 的 UUID,能夠調用 UUIDWithString 方法來經過預約義的 16位 UUID來建立 CBUUID 對象。spa

CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
複製代碼

當你經過預約義的 16位 UUID 來建立 CBUUID 對象時,CoreBluetooth 會基於128位Bluetooth Base UUID 填充剩下的的 UUID 位。

爲你定製的 Services 和 Characteristics 生成 UUID

你的 Service 和 Characteristic 的UUID也許可能沒有被 Bluetooth UUIDs 預約義,若是沒有被預約義,你須要手動生成你本身的 128位 UUID 來表示 Service 和 Characteristic。

經過命令行命令 uuidgen 能夠生成 128位的 UUID,打開你的 Terminal(終端),經過這種方式依次爲你的 Services 和 Characteristics 生成一個 UUID (用連字符鏈接起來的字符串)來標識。舉例以下:

$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
複製代碼

你能夠用上面方法生成的 UUID 調用 UUIDWithString 方法來建立一個 CBUUID 對象。

CBUUID *myCustomServiceUUID =
        [CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
複製代碼
構建你的 服務特徵樹

當你爲每一個 Service 和 Characteristic 建立 CBUUID 對象後,你能夠建立 mutable Service(可變服務) 和 mutable Characteristic(可變特徵),而後以樹形的方式組織它們。舉個例子,若是你如今有一個 Characteristic 的 UUID,你能夠經過調用 CBMutableCharacteristic 類的 initWithType:properties:value:permissions: 方法生成一個 mutable Characteristic。

myCharacteristic =
        [[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
         properties:CBCharacteristicPropertyRead
         value:myValue permissions:CBAttributePermissionsReadable];
複製代碼

當你建立 mutable Characteristic 的時候,你能夠指定它的 properties(屬性)、value(值)和 permissions(權限許可),你指定的 properties 和 permissions 決定這個 Characteristic 的值是否能夠讀或者寫,或者鏈接的 Central 可否訂閱該 Characteristic 的值。下面的示例中,Characteristic 的值是被指定爲可讀的。關於 mutable Characteristic 的 properties 和 permissions 詳情能夠查閱 CBMutableCharacteristic Class Reference.

提示:若是你指定了 Characteristic 的值,那麼該值將被緩存而且該 Characteristic 的 properties 和 permissions 將被設置爲可讀的。所以,若是你須要 Characteristic 的值是可寫的,或者你但願在 Service 發佈後,Characteristic 的值在 lifetime(生命週期)中依然能夠更改,你必須將該 Characteristic 的值指定爲 nil。經過這種方式能夠確保 Characteristic 的值,在 Peripheral Manager 收到來自鏈接的 Central 的讀或者寫請求的時候,可以被動態處理。

既然你建立了一個 mutable Characteristic,你也能經過調用 CBMutableService 類的 initWithType:primary: 方法建立一個 mutable Service。以下所示:

myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
複製代碼

在示例代碼中,第二個參數被指定爲 YES,用來代表該 Service 是 Primary(主要的),而不是 secondary(次要的)。一個 Primary Service 用來描述這個設備的主要功能,還能夠用來引用其餘的 Service。一個 Secondary Service 用來描述的是上下文中相關的或者被引用的 Service。舉個例子,從心率傳感器中獲取心率的服務是 primary Service,而獲取傳感器電量的服務就能夠被視爲 secondary Service 。

當你建立完 Service 後。你須要設置 Service 的 Characteristic 數組屬性,以下:

myService.characteristics = @[myCharacteristic];
複製代碼

發送你的 Services 和 Characteristics

當你構建好服務特徵樹後,下一步就是按照 BLE 的規範發佈到設備的服務特徵庫中,用 CoreBluetooth 能夠很輕鬆的完成這一步,只須要調用 CBPeripheralManager類 的 addService: 方法就能夠了。 示例代碼以下:

[myPeripheralManager addService:myService];
複製代碼

當你調用該方法發佈服務時,Peripheral Manager 會調用 peripheralManager:didAddService:error: 方法進行代理回調,實現這個代理方法能夠獲取到產生的錯誤,示例代碼以下:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
            didAddService:(CBService *)service
                    error:(NSError *)error {
    if (error) {
        NSLog(@"Error publishing service: %@", [error localizedDescription]);
    }
    // ...
}
複製代碼

提示:當你發佈 Service 和相關的 Characteristic 到 Peripheral 的數據庫中後,設備已經將數據緩存,你不能再改變它了。

廣播你的 Service

當你發佈你的 Service 和 Characteristic 到設備的服務特徵庫時,你能夠廣播一些服務給正在監聽的 Central,你能夠經過調用 CBPeripheralManager 類的 startAdvertising: 方法來開始廣播,傳入的字典是要廣播的數據。

[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
        @[myFirstService.UUID, mySecondService.UUID] }];
複製代碼

在示例代碼中,傳入的字典中惟一的 key 是 CBAdvertisementDataServiceUUIDsKey,用一個包含 CBUUID 對象的數組來表示你想要廣播的服務的 UUID。你在字典中能夠指定的其餘 key 在 Advertisement Data Retrieval Keys 中有詳細說明。也就是說,僅有 CBAdvertisementDataLocalNameKeyCBAdvertisementDataServiceUUIDsKey 這兩個 key 支持 Peripheral Manager 對象。

當你在本地設備中廣播一些數據時,Peripheral Manager 會經過 peripheralManagerDidStartAdvertising:error: 方法來代理回調。若是你的設備不能廣播而發生錯誤時,實現這個代理方法能夠獲取產生的錯誤:

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
                                       error:(NSError *)error {
 
    if (error) {
        NSLog(@"Error advertising: %@", [error localizedDescription]);
    }
    // ...
}
複製代碼

提示:廣播數據方法會被盡力執行,由於空間是有限的和多個 APP 可能同時須要廣播數據,更多詳情能夠查閱關於 startAdvertising: 方法的討論。

當你的 APP 在後臺運行時也會影響廣播的行爲,這一內容將會在下一篇中進行討論。

響應 Central 的讀寫請求

當你鏈接一個或多個 Central 後,你可能會收到讀或者寫的請求,對這些請求做出響應須要採起恰當的方式,下面的示例代碼將會描述如何處理這些請求。

當一個鏈接的 Central 發送讀取某個 Characteristic 數據的請求時,Peripheral Manager 會調用 peripheralManager:didReceiveReadRequest: 方法進行代理回調。代理方法以 CBATTRequest 對象的方式來傳遞請求,它包含一些請求的屬性。

好比,當你收到一個讀取 Characteristic 值的簡單請求時,能夠經過代理方法回調的 CBATTRequest 對像來判斷 Central 指定要讀取的 Characteristic 是否和設備服務庫中的 Characteristic 是否相匹配。你能夠開始實現這個代理方法,示例代碼以下:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
    didReceiveReadRequest:(CBATTRequest *)request {
 
    if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
        // ...
    }
}
複製代碼

若是 Characteristic 的 UUID 可以匹配,下一步就是確保讀取請求的位置沒有超出 Characteristic 的值的邊界。以下面代碼所示,你能夠經過使用 CBATTRequest 對象的 offset 屬性來確保讀取請求沒有嘗試讀取範圍以外的數據。

if (request.offset > myCharacteristic.value.length) {
    [myPeripheralManager respondToRequest:request
        withResult:CBATTErrorInvalidOffset];
    return;
}
複製代碼

假如讀取請求的 offset(偏移)已經確認,如今就能夠設置請求的 Characteristic 的屬性(默認值爲 nil)爲你設備中的 Characteristic 的值了,你應該重視讀取請求的偏移:

request.value = [myCharacteristic.value
        subdataWithRange:NSMakeRange(request.offset,
        myCharacteristic.value.length - request.offset)];
複製代碼

設置完值後,經過調用 respondToRequest:withResult: 方法並傳入 request(更新值後的)和 請求的結果參數來對 Remote Central 的請求做出響應表示請求已經被成功處理。示例代碼以下:

[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
// ...
複製代碼

只要代理方法 peripheralManager:didReceiveReadRequest: 方法被回調,就須要準確的調用 respondToRequest:withResult: 方法。

提示:若是 Characteristic 的 UUID 不匹配,或者由於某種緣由不能徹底讀取,沒必要去填充請求,直接調用 respondToRequest:withResult: 方法並提供一個表示失敗的結果便可。你可能指定的結果列表見 CBATTError Constants 常量枚舉。

處理鏈接的 Central 寫入請求也比較易懂。當 Central 發送一個寫入請求給一個或多個你的 Characteristic 時,Peripheral Manager 會經過 peripheralManager:didReceiveWriteRequests: 方法來代理回調。這是,代理方法會傳遞一個包含一個或多個 CBATTRequest 對象的數組給你,數組中的每一個對象都表明一個寫入請求。當你肯定寫入請求可以處理時,你能夠設置 Characteristic 的值,示例代碼以下:

myCharacteristic.value = request.value;
複製代碼

雖然上述例子沒有證實這一點,但當你給 Characteristic 寫數據的時候,你應該確保請求的 offset 屬性的範圍有效。

就像你響應讀取請求同樣,只要代理方法 peripheralManager:didReceiveWriteRequest: 方法被回調,就須要準確無誤的調用 respondToRequest:withResult: 方法。也就是說,respondToRequest:withResult: 方法指望有一個 CBATTRequest 對象,即便你可能經過 peripheralManager:didReceiveWriteRequests: 代理方法接收到一個包含 CBATTRequest 對象的數組,你也應該傳入數組中的第一個對象,示例代碼以下:

[myPeripheralManager respondToRequest:[requests objectAtIndex:0]
        withResult:CBATTErrorSuccess];
複製代碼

提示:將多請求視爲單一請求來對待,若是個別的請求不能被填充,你就沒必要填充其他的請求了,直接調用 respondToRequest:withResult: 方法並提供一個表示失敗的結果便可。

發送更新 Characteristic 的通知給訂閱的 Central

鏈接的 Central 常常會訂閱一個或多個 Characteristic 的值,當這些值發生變化時,你應該發送通知給訂閱的 Central 。

當一個鏈接的 Central 訂閱一個或多個你的 Characteristic 值時,Peripheral Manager 會經過 peripheralManager:central:didSubscribeToCharacteristic: 方法來代理回調。示例代碼以下:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
                  central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
 
    NSLog(@"Central subscribed to characteristic %@", characteristic);
    // ...
}

複製代碼

將上述的代理方法做爲一個線索來開始給 Central 發送更新後的值。

接着,獲取更新後的 Characteristic 的值,經過調用 CBPeripheralManager類的 updateValue:forCharacteristic:onSubscribedCentrals: 方法來給 Central 發送通知。示例代碼以下:

NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
    forCharacteristic:characteristic onSubscribedCentrals:nil];
複製代碼

當你調用這個方法給訂閱的 Central 發送通知時,你能夠經過最後的那個參數來指定要發送的 Central,示例代碼中的參數爲 nil,代表將會發送通知給全部鏈接且訂閱的 Central,沒有訂閱的 Central 則會被忽略。

updateValue:forCharacteristic:onSubscribedCentrals: 方法會返回一個 Boolean 類型的值來表示通知是否成功的發送給訂閱的 Central 了,若是 base queue (基礎隊列)滿載,該方法會返回 NO,當傳輸隊列存在更多空間時,Peripheral Manager 則會調用 peripheralManagerIsReadyToUpdateSubscribers: 代理方法進行回調。你能夠實現這個代理方法,在方法中再次調用 updateValue:forCharacteristic:onSubscribedCentrals: 方法發送通知給訂閱的 Central。

提示:用通知發送單個數據包給訂閱的 Central,就是說,一旦訂閱的 Central 發行更新時,你就應該調用 updateValue:forCharacteristic:onSubscribedCentrals: 方法用單一通知發送所有的更新值。

並非全部的數據都是經過通知來傳輸的,這主要取決於你的 Characteristic 的值的大小,只有當 Central 調用 CBPeripheral類的 readValueForCharacteristic: 方法時,你能夠檢索所有的值。

參考文獻

一、Performing Common Peripheral Role Tasks

結束語

歡迎在本文下面留言一塊兒交流心得...

相關文章
相關標籤/搜索