To conserve power, most Bluetooth devices operate as low-power, 1 mW radios (Class 3 radio power).
Bluetooth is both a hardware-based radio system and a software stack that specifies the linkages between layers.
The Bluetooth specification defines a wide range of profiles, describing many different types of tasks, some of which have not yet been implemented by any device or system. By following the profiles’s procedures, developers can be sure that the applications they create will work with any device that conforms to the Bluetooth specification.
發送搜索廣播並等待確認回應, 查詢范圍內的所有設備.
Core Bluetooth Overview
import
和傳統的客戶-服務器結構類似, 從設備一般都是為主設備提供數據. 主設備則使用這些數據來完成特定任務.
從設備會將一些數據以包(advertising package)的形式進行廣播.
An advertising packet is a relatively small bundle of data that may contain useful information about what a peripheral has to offer, such as the peripheral’s name and primary functionality.
這種數據包實際就是一小團數據, 包含比如從設備的功能或名字等信息.
比如一個溫度計就會廣播它的功能是: 可以提供當前室內溫度.
In Bluetooth low energy, advertising is the primary way that peripherals make their presence known.
BLE中, 廣播是從設備聲明自己存在的主要途徑.
主設備可以掃描和監聽任何從設備, 只要從設備正在廣播信息.
主設備會要求連接到它發現的從設備, 從而獲取廣播數據.
主設備連接到從設備的目的就是獲取從設備提供的數據.
那從設備的數據是如何構造出來的?
從設備中可能會提供一個或多個服務, 或提供當前的連接信號強度等信息.
A service is a collection of data and associated behaviors for accomplishing a function or feature of a device (or portions of that device).
服務指的是一組數據以及同數據同數據相關聯的行為, 它們共同作用來完成設備的部分或全部功能.
比如心跳檢測設備的一個服務就是獲取它的心跳傳感器數據.
服務又是由特征或子服務(即其他服務的引用)所組成.
特征用於提供詳細信息.
比如心跳檢測設備的某個服務, 該服務包含兩個特征, 其中一個特征提供心跳傳感器當前處在身體哪個位置, 另外一個特征則提供心跳檢測數據.
主從設備間建立連接之後, 主設備便可以訪問從設備的所有服務(廣播數據可能只包含一部分可用服務).
主設備可以讀寫服務特征, 以這樣的方式同從設備進行交互.
比如APP可以從藍牙溫度計上獲取當前室內溫度, 或者說APP可以設置藍牙恆溫機上的恆定溫度.
當從APP連接其他設備時, 就是在主設備側編程. 並且絕大多數都是這種情況.
主設備: CBCentralManager對象表示.
該對象用於管理掃描到的或已連接的從對象. 包括掃描(正在廣播的), 發現(服務和特征), 連接(正在廣播的)從設備.
從設備: CBPeripheral對象.
服務: CBService 對象.
特征: CBCharacteristic對象.從設備上服務和特征的基本結構如下圖所示:
即從設備上有多個服務, 服務中又可能包含多個特征.<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPii9q7G+tdjJ6LG41/fOqrTTyeixuL3awtQmaGVsbGlwOyk8L3A+DQo8aDIgaWQ9"2-將本地設備作為主設備">2 將本地設備作為主設備
本地設備作為主設備時, 通常都會進行如下工作: 發現並連接到可用從設備, 然後對從設備數據進行讀寫操作.
步驟通常都是:
創建一個主設備對象 掃描並連接到正在廣播的從設備 發現從設備提供的各種數據服務 對服務中的特征進行讀寫操作(另外還可以設置當從設備數據改變時得到通知) 關閉連接myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
上面代碼將self作為主設備對象代理, 即主設備對象的事件都交由self處理, 並且將queue設置為nil, 即主設備對象的事件處理是在main queue上完成.
建立主設備對象後, 首先它會調用代理中的centralManagerDidUpdateState:方法, 需要實現這個方法來保證BLE正常工作. (實現細節詳見:CBCentralManagerDelegate Protocol Reference).
2.2 發現正在廣播的從設備
前面就講到了, 主設備要發現從設備的話, 必須是該從設備正在進行廣播(advertising). 只要從設備在廣播, 就可以被發現.
使用主設備對象的scanForPeripheralsWithServices:options:方法來發現從設備:
[myCentralManager scanForPeripheralsWithServices:nil options:nil];
上面方法第一個參數nil, 表示發現任何設備, 不管它的服務本程序是否支持. 但實際工作中, 需要指定一個CBUUID數組, 用UUID代表每個服務, 這樣就可以只掃描到擁有特定服務的從設備.
從設備服務和特征都是使用UUID進行統一標識, 那麼什麼是UUID和CBUUID?
從設備中的服務和特征都是通過128位的UUID進行標識, 在Core Bluetooth框架中是CBUUID對象. 大多數常用的服務和特征的UUID都被SIG組織進行了預定義並且公布出來了, 並且為了方便使用都縮短成了16位.
比如SIG預定義的心跳檢測服務UUID為180D, 是它的對應128位UUID “0000180D-0000-1000-8000-00805F9B34FB” 中間部分截取出來的. 128位這個UUID在藍牙4.0規范卷3, F部分, 3,2,1節定義.
CBUUID類提供了一個類方法, 方便生成UUID, 使用方法如下所示:
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
一般情況下, 如果指定的是16位UUID, CoreBluetooth會自動自動將其填充為規范內的128位UUID.
如果自己有特殊的服務, 則可以使用終端生成自己的UUID
$ uuidgen 71DA3FD1-7E10-41C1-B16F-4430B506CDE7
則可以自己構造對應CBUUID:
CBUUID *myCustomServiceUUID = [CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
當使用scanForPeripheralsWithServices方法掃描從設備時, 每掃描到一個從設備, CBCentralManager對象就會調用一次代理中的
- centralManager:didDiscoverPeripheral:advertisementData:RSSI:方法.
因為掃描到一個從設備, 就會返回一個CBPeripheral對象, 則可以像如下這樣打印掃描到的從設備:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"Discovered %@", peripheral.name); //打印設備名
...
若掃描到了需要的設備, 則需要關閉掃描以節約電量:
[myCentralManager stopScan]; //停止掃描
NSLog(@"Scanning stopped");
2.3 連接到從設備
調用如下方法連接到某從設備:
[myCentralManager connectPeripheral:peripheral options:nil];
假如連接成功, 主設備對象會調用下面的代理方法:
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"Peripheral connected");
...
建立連接完畢, 還需要設置從設備對象的代理, 以保證正常接收回叫, 如下所示:
peripheral.delegate = self;
2.4 發現從設備的服務
連接建立完畢, 就可以使用從設備的服務了, 首先即發現從設備的可用服務.
由於從設備的廣播數據包大小收到限制, 所以要向從設備發現服務, 而非在廣播數據包中發現服務(前面也說過, 廣播數據包中只有一部分的可用服務).
使用如下格式獲取從設備的所有服務:
[peripheral discoverServices:nil];
該參數指定為nil表示發現所有服務, 當然實際APP中只會去發現部分服務. 也是指定服務UUID.
當發現指定服務之後, 從設備對象會調用代理對象中的
peripheral:didDiscoverServices:方法. CB框架會創建一個CBService對象數組, 每個對象代表一個服務, 通過這個數組就可以訪問每個服務了:
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service %@", service);
...
}
...
2.5 發現服務中的特征
當發現指定服務後, 需要獲取服務中特征數據.
此時需要使用從設備對象的discoverCharacteristics:forService:方法:
NSLog(@"Discovering characteristics for service %@", interestingService);
[peripheral discoverCharacteristics:nil forService:interestingService];
同樣地, 實際使用時不會設置nil來獲取所有特征, 而是通過UUID獲取某些指定的特征.
當發現特征後, 從設備對象會調用代理對象中的peripheral:didDiscoverCharacteristicsForService:error:方法.
CB框架會自動生成一個CBCharacteristic對象數組, 裡面的每個對象代表一個發現的特征:
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"Discovered characteristic %@", characteristic);
...
}
...
2.6 獲取特征中包含的數據
一個特征中包含服務的詳細信息, 這個信息在特征中是單個值.
比如體溫測量服務中的溫度特征一般包含的就是測量到的溫度數值, 那麼就可以通過這個特征獲取溫度, 或是直接監控這個特征.
2.6.1 讀取特征數據
當發現特定服務中的指定特征後, 就可以獲取該特征中的數據了.
首先使用從設備對象的readValueForCharacteristic:方法指定需要讀取的特征:
NSLog(@"Reading value for characteristic %@", interestingCharacteristic);
[peripheral readValueForCharacteristic:interestingCharacteristic];
指定需要讀取的特征後, 從設備對象就會調用代理對象中的
peripheral:didUpdateValueForCharacteristic:error:方法獲取數據, 若數據成功獲取, 就可以通過特征對象的value屬性來訪問:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
NSData *data = characteristic.value;
// parse the data as needed
...
需要注意的是: 並非所有的特征都保證其value是可讀的, 可以設置特征對象的 CBCharacteristicPropertyRead屬性來決定該特征的value是否可讀. 若某特征的value設置為不可讀, 讀取該value時,
peripheral:didUpdateValueForCharacteristic:error:代理方法就會返回一個相應的error.
2.6.2 訂閱特征數據
上面讀取數據是訪問特征數據的一種方式, 但對於訪問經常改變的數據, 更加高效的方式是直接訂閱該特征數據.
比如某時段的心跳次數, 這個數據是一直在改變的, 則應通過訂閱進行訪問.
當訂閱某特征數據後, 當該數據改變時, 就會從從設備對象收到一個通知(Notification).
使用從設備對象方法
setNotifyValue:forCharacteristic:來訂閱指定特征, 第一個參數設YES表示數據改變時發送通知:
[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];
當訂閱或解除訂閱某特征時, 從設備對象都會調用代理對象的
peripheral:didUpdateNotificationStateForCharacteristic:error:方法, 並且當訂閱由於某種原因失敗時, 可以通過這個方法查看失敗原因:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error changing notification state: %@",
[error localizedDescription]);
}
...
並非所有特征都被設置為可訂閱, 可以進行相應設置.
當訂閱成功後, 若該特征數據改變, 從設備就會通知你的APP. 並且數據改變時, 從設備對象會調用
peripheral:didUpdateValueForCharacteristic:error:代理方法, 就可以在這個方法中像上面一樣獲取特征數據了.
2.7 寫特征數據
暫略.
3 iOS藍牙後台處理!!!
iOS程序的前台和後台狀態差別很大, 需要理解….詳見官文…
對於CB框架而言, 當不具有後台運行模式的APP轉入後台後, 就不會接收到藍牙相關通知.
總的來說, 藍牙相關APP分兩種: 一種是只能前台運行的, 一種是具有後台運行模式的.
只能前台運行的程序, 當轉入後台後短時間內就會被置為suspended掛起. 並且所有藍牙相關事件在APP掛起時都會被放入到一個隊列中, 當程序恢復到前台運行時再全部交由APP處理.雖然應用相關的事件只能當應用恢復前台運行時才進行處理, 但CB提供了一些手段來通知用戶這個程序相關的事件發生. 如應用處於後台或掛起時建立連接成功的通知. 詳見文檔…
若想要後台處理藍牙相關事件, 需要在應用的info.plist中修改設置, 設置應用的後台運行模式.
有兩個模式, 一個是作為主設備的後台運行, 一個是作為從設備的後台運行.
但後台運行會導致耗電多, 所以應在需要時才使用, 使用完盡快轉入掛起.
Because performing many Bluetooth-related tasks require the active use of an iOS device’s onboard radio—and, in turn, radio usage has an adverse effect on an iOS device’s battery life—try to minimize the amount of work you do in the background. Apps woken up for any Bluetooth-related events should process them and return as quickly as possible so that the app can be suspended again.
Any app that declares support for either of the Core Bluetooth background executions modes must follow a few basic guidelines:
Apps should be session based and provide an interface that allows the user to decide when to start and stop the delivery of Bluetooth-related events. Upon being woken up, an app has around 10 seconds to complete a task. Ideally, it should complete the task as fast as possible and allow itself to be suspended again. Apps that spend too much time executing in the background can be throttled back by the system or killed. Apps should not use being woken up as an opportunity to perform extraneous tasks that are unrelated to why the app was woken up by the system.
一種實際方案是使用CB提供的程序狀態保存和恢復機制.
3.1 添加程序狀態保存和恢復
需要按下列步驟進行操作, 以添加這個可選功能:
在建立主設備對象時設置合適的選項. 在代碼中實現合適的恢復運行代碼 實現合適的恢復APP運行的代理方法 更新主或從對象(可選).