這篇文章的目的:教你實現設備的掃描,連接,數據收發,藍牙數據解析。如果在實現上面任一功能遇到問題時,歡迎留下你的問題,我將進行補充,對於說法有誤的地方也請老司機予以指正。
目錄
思維導圖
第一次做圖,大家湊合着看哈。這張是我總結的藍牙知識的結構圖,下面的內容將圍繞這些東西展開進行。 這張是藍牙連接發送數據的流程圖,下文進入coding階段的講解順序,大家先有個大概印象,等閱讀完本文再回來看這張圖將理解的更深一些。蘋果對藍牙設備有什麼要求
BLE:bluetouch low energy,藍牙4.0設備因爲低功耗,所有也叫作BLE。蘋果在iphone4s及之後的手機型號開始支持藍牙4.0,這也是最常見的藍牙設備。低於藍牙4.0協議的設備需要進行MFI認證,關於MFI認證的申請工作可以看這裏:關於MFI認證你所必須要知道的事情
在進行操作藍牙設備前,我們先下載一個藍牙工具LightBlue
,它可以輔助我們的開發,在進行藍牙開發之前建議先熟悉一下LightBlue這個工具。
操作藍牙設備使用什麼庫
蘋果自身有一個操作藍牙的庫CoreBluetooth.framework
,這個是大多數人員進行藍牙開發的首選框架,除此之外目前github還有一個比較流行的對原生框架進行封裝的三方庫BabyBluetooth,它的機制是將CoreBluetooth中衆多的delegate寫成了block方法,有興趣的同學可以瞭解下。下面主要介紹的是原生藍牙庫的知識。
中心和外圍設備
如圖所示,電腦、Pad、手機作爲中心,心跳監聽器作爲外設,這種中心外設模式是最常見的。簡單理解就是,發起連接的是中心設備(Central),被連接的是外圍設備(Peripheral),對應傳統的客戶機-服務器體系結構。Central能夠掃描偵聽到,正在播放廣告包的外設。
服務與特徵
外設可以包含一個或多個服務(CBService),服務是用於實現裝置的功能或特徵數據相關聯的行爲集合。 而每個服務又對應多個特徵(CBCharacteristic),特徵提供外設服務進一步的細節,外設,服務,特徵對應的數據結構如下所示
如何掃描藍牙
在進行掃描之前我們需要,首先新建一個類作爲藍牙類,例如FYBleManager
,寫成單例,作爲處理藍牙操作的管理類。引入頭文件#import <CoreBluetooth/CoreBluetooth.h>
CBCentralManager
是藍牙中心的管理類,控制着藍牙的掃描,連接,藍牙狀態的改變。
1、初始化
dispatch_queue_t centralQueue = dispatch_queue_create(“centralQueue",DISPATCH_QUEUE_SERIAL);
NSDictionary *dic = @{CBCentralManagerOptionShowPowerAlertKey : YES, CBCentralManagerOptionRestoreIdentifierKey : @"unique identifier"};
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:dic];
複製代碼
CBCentralManagerOptionShowPowerAlertKey
對應的BOOL值,當設爲YES時,表示CentralManager初始化時,如果藍牙沒有打開,將彈出Alert提示框
CBCentralManagerOptionRestoreIdentifierKey
對應的是一個唯一標識的字符串,用於藍牙進程被殺掉恢復連接時用的。
2、掃描
//不重複掃描已發現設備
NSDictionary *option = @{CBCentralManagerScanOptionAllowDuplicatesKey : [NSNumber numberWithBool:NO],CBCentralManagerOptionShowPowerAlertKey:YES};
[self.centralManager scanForPeripheralsWithServices:nil options:option];
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
複製代碼
掃面方法,serviceUUIDs
用於第一步的篩選,掃描此UUID的設備
options有兩個常用參數:CBCentralManagerScanOptionAllowDuplicatesKey
設置爲NO表示不重複掃瞄已發現設備,爲YES就是允許。CBCentralManagerOptionShowPowerAlertKey
設置爲YES就是在藍牙未打開的時候顯示彈框
3、CBCentralManagerDelegate代理方法
在初始化的時候我們調用了代理,在CoreBluetooth中有兩個代理,
- CBCentralManagerDelegate
- CBPeripheralDelegate
iOS的命名很友好,我們通過名字就能看出,上面那個是關於中心設備的代理方法,下面是關於外設的代理方法。我們這裏先研究CBCentralManagerDelegate
中的代理方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
複製代碼
這個方法標了@required
是必須添加的,我們在self.centralManager初始換之後會調用這個方法,回調藍牙的狀態。狀態有以下幾種:
typedef NS_ENUM(NSInteger, CBCentralManagerState{
CBCentralManagerStateUnknown = CBManagerStateUnknown,//未知狀態
CBCentralManagerStateResetting = CBManagerStateResetting,//重啓狀態
CBCentralManagerStateUnsupported = CBManagerStateUnsupported,//不支持
CBCentralManagerStateUnauthorized = CBManagerStateUnauthorized,//未授權
CBCentralManagerStatePoweredOff = CBManagerStatePoweredOff,//藍牙未開啓
CBCentralManagerStatePoweredOn = CBManagerStatePoweredOn,//藍牙開啓} NS_DEPRECATED(NA, NA, 5_0, 10_0, "Use CBManagerState instead”
);
複製代碼
該枚舉在iOS10之後已經廢除了,系統推薦使用CBManagerState
,類型都是對應的
typedef NS_ENUM(NSInteger, CBManagerState{
CBManagerStateUnknown = 0,
CBManagerStateResetting,
CBManagerStateUnsupported,
CBManagerStateUnauthorized,
CBManagerStatePoweredOff,
CBManagerStatePoweredOn,
} NS_ENUM_AVAILABLE(NA, 10_0);
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
複製代碼
peripheral是外設類
advertisementData
是廣播的值,一般攜帶設備名,serviceUUIDs
等信息
RSSI絕對值越大,表示信號越差,設備離的越遠。如果想裝換成百分比強度,(RSSI+100)/100,(這是一個約數,藍牙信號值並不一定是-100 - 0的值,但近似可以如此表示)
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict;
複製代碼
在藍牙於後臺被殺掉時,重連之後會首先調用此方法,可以獲取藍牙恢復時的各種狀態
如何連接
在掃面的代理方法中,我們連接外設名是MI的藍牙設備
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"advertisementData:%@,RSSI:%@",advertisementData,RSSI);
if([peripheral.name isEqualToString:@"MI"]){
[self.centralManager connectPeripheral:peripheral options:nil];//發起連接的命令
self.peripheral = peripheral;
}
}
複製代碼
連接的狀態
對應另外的CBCentralManagerDelegate
代理方法
連接成功的回調
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
複製代碼
連接失敗的回調
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
複製代碼
連接斷開的回調
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
複製代碼
連接成功之後並沒有結束,還記得CBPeripheral
中的CBService
和CBService
中的CBCharacteristic
嗎,對數據的讀寫是由CBCharacteristic
控制的。我們先用lightblue連接小米手環爲例,來看一下,手環內部的數據是不是我們說的那樣。
其中ADVERTISEMENT DATA
顯示的就是廣播信息。
iOS藍牙無法直接獲取設備藍牙MAC地址,可以將MAC地址放到這裏廣播出來
FEEO
是ServiceUUIDs
,裏面的FF01
、FF02
是CBCharacteristic的UUID
Properties
是特徵的屬性,可以看出FF01
具有讀的權限,FF02
具有讀寫的權限。特徵擁有的權限類別有如下幾種:
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties{
CBCharacteristicPropertyBroadcast = 0x01,
CBCharacteristicPropertyRead = 0x02,
CBCharacteristicPropertyWriteWithoutResponse = 0x04,
CBCharacteristicPropertyWrite = 0x08,
CBCharacteristicPropertyNotify = 0x10,
CBCharacteristicPropertyIndicate = 0x20,
CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
CBCharacteristicPropertyExtendedProperties = 0x80,
CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,
CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200
};
複製代碼
如何發送並接收數據
通過上面的步驟我們發現CBCentralManagerDelegate
提供了藍牙狀態監測、掃描、連接的代理方法,但是CBPeripheralDelegate
的代理方法卻還沒使用。別急,馬上就要用到了,通過名稱判斷這個代理的作用,肯定是跟Peripheral
有關,我們進入系統API,看它的代理方法都有什麼,因爲這裏的代理方法較多,我就挑選幾個常用的拿出來說明一下。
1、代理方法
//發現服務的回調
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
//發現特徵的回調
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
//讀數據的回調
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
//是否寫入成功的回調
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
複製代碼
2、步驟
通過這幾個方法我們構建一個流程:連接成功->獲取指定的服務->獲取指定的特徵->訂閱指定特徵值->通過具有寫權限的特徵值寫數據->在didUpdateValueForCharacteristic
回調中讀取藍牙反饋值
解釋一下訂閱特徵值:特徵值具有Notify權限纔可以進行訂閱,訂閱之後該特徵值的value發生變化纔會回調didUpdateValueForCharacteristic
3、實現上面流程的實例代碼
//連接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//連接成功之後尋找服務,傳nil會尋找所有服務
[peripheral discoverServices:nil];
}
//發現服務的回調
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if (!error) {
for (CBService *service in peripheral.services) {
NSLog(@"serviceUUID:%@", service.UUID.UUIDString);
if ([service.UUID.UUIDString isEqualToString:ST_SERVICE_UUID]) {
//發現特定服務的特徵值
[service.peripheral discoverCharacteristics:nil forService:service];
}
}
}
}
//發現characteristics,由發現服務調用(上一步),獲取讀和寫的characteristics
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
//有時讀寫的操作是由一個characteristic完成
if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]) {
self.read = characteristic;
[self.peripheral setNotifyValue:YES forCharacteristic:self.read];
} else if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_WRITE]) {
self.write = characteristic;
}
}
}
//是否寫入成功的代理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"===寫入錯誤:%@",error);
}else{
NSLog(@"===寫入成功");
}
}
//數據接收
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]){ //獲取訂閱特徵回覆的數據
NSData *value = characteristic.value;
NSLog(@"藍牙回覆:%@",value);
}
}
複製代碼
比如我們要獲取藍牙電量,由硬件文檔查詢得知該指令是**0x1B9901**
,那麼獲取電量的方法就可以寫成
- (void)getBattery{
Byte value[3]={0};
value[0]=x1B;
value[1]=x99;
value[2]=x01;
NSData * data = [NSData dataWithBytes:&value length:sizeof(value)];
//發送數據
[self.peripheral writeValue:data forCharacteristic:self.write type:CBCharacteristicWriteWithoutResponse];
}
複製代碼
如果寫入成功,我們將會在didUpdateValueForCharacteristic
方法中獲取藍牙回覆的信息。
如何解析藍牙數據
如果你順利完成了上一步的操作,並且看到了藍牙返回的數據,那麼恭喜你,藍牙的常用操作你已經瞭解大半了。因爲藍牙的任務大部分就是圍繞發送指令,獲取指令,將藍牙數據呈現給用戶。上一步我們已經獲取了藍牙指令,但是獲取的卻是0x567b0629
這樣的數據,這是什麼意思呢。這時我們參考硬件文檔,看到這樣一段:
對數據解析的流程就是:判斷校驗和是否正確,是不是一條正確的數據->該條數據是不是我們需要的電量數據,即首字節爲0x567b
->根據定義規則解析電量,傳給view顯示。其中第一步校驗數據,視情況而定,也有不需要的情況。
擴展
*轉載請私信聯繫