歡迎訪問個人博客 muhlenXi,該文章出自個人博客, 歡迎轉載,轉載請註明來源: muhlenxi.com/2017/05/01/…。html
在這一節,你將會學到,如何經過 CoreBluetooth 框架來實現 Local Peripheral 方面的功能和代理方法。數據庫
在 iOS BLE 開發小記[2]中,你已經學到了如何在 Central 方面去調用 BLE 的經常使用方法。在這一節中,你將學習用 CoreBluetooth 框架來調用 Peripheral 方面 BLE 的經常使用方法。經過本文的示例代碼,將會引導你開發一個將你的 Local 設備實現爲 Local Peripheral。你將會從中學到:數組
或許你發現示例代碼過於簡單和抽象,你須要在你的 App 中作些恰當的練習來掌握這些內容。更高級的技巧和最佳實踐在後續的文章中將會講解。緩存
在 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.學習
在第一節中,咱們瞭解到,一個 Local Peripheral 採用樹形結構來組織 Services 和 Characteristics 的數據。所以必須採用樹形結構方式來設置 Local Peripheral 的 Services 和 Characteristics。你第一步要作的是搞清和理解 Service 和 Characteristic 是如何標識的。fetch
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 位。
你的 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];
複製代碼
當你構建好服務特徵樹後,下一步就是按照 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 和 Characteristic 到設備的服務特徵庫時,你能夠廣播一些服務給正在監聽的 Central,你能夠經過調用 CBPeripheralManager
類的 startAdvertising:
方法來開始廣播,傳入的字典是要廣播的數據。
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
複製代碼
在示例代碼中,傳入的字典中惟一的 key 是 CBAdvertisementDataServiceUUIDsKey
,用一個包含 CBUUID 對象的數組來表示你想要廣播的服務的 UUID。你在字典中能夠指定的其餘 key 在 Advertisement Data Retrieval Keys 中有詳細說明。也就是說,僅有 CBAdvertisementDataLocalNameKey
和 CBAdvertisementDataServiceUUIDsKey
這兩個 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 發送讀取某個 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:
方法並提供一個表示失敗的結果便可。
鏈接的 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
歡迎在本文下面留言一塊兒交流心得...