1.CoreBluetooth.
iOS藍牙的相關操作由CoreBluetooth.framework進行管理。核心主要是兩種場景:peripheral和central, 可以理解成外設和中心。
在此主要用到了藍牙作為中心設備通訊連接硬件的服務。中心模式流程為:
1.建立中心角色;
2.掃描外設 (discover);15:25:21
3.鏈接外設 (connect);
4.掃描外設種的服務和特征
4.1 獲取外設的服務 services;
4.2 獲取外設的特征 characteristics;獲取特征的descriptor和descriptor的值;
5.與外設做數據交互,讀寫數據;
6.訂閱 characteristics 的通知;
7.斷開。
其中:peripheral,central == 外設和中心,發起連接的時central,被連接的設備為perilheral;
service and characteristic === 服務和特征 每個設備會提供服務和特征,類似於服務端的api,但是機構不同。每個外設會有很多服務,每個服務中包含很多字段,這些字段的權限一般分為 讀read,寫write,通知notiy幾種,就是我們連接設備后具體需要操作的內容。
2.代碼實現
在實際開發過程中,可以根據需求將藍牙操作寫成單例模式,便於整體規划開發。
首先,硬件說明文檔中 一般都會給你 三個UUID類似下面這種:
// 設備service UUID #define BLUETOOTH_SERVICE_UUID @"6E400001-B5A3-F393-E0xxx" // service 下 用來 寫數據的 characteristic 的UUID #define BLUETOOTH_CHARACTERISTIC_WRITE_UUID @"6E400002-B5A3-F393-E0xxx" // service 下 用來 讀數據的 characteristic 的UUID ,其實這個讀數據一般是在寫完之后,拿到寫入成功硬件的返回通知,所以其實是通知的uuid #define BLUETOOTH_CHARACTERISTIC_READ_UUID @"6E400003-B5A3-F393-E0xxx" //// characteristic 有properite 屬性,其中有對應的 讀,寫,通知 等權限,不同的權限對應不同的功能。
1.建立中心角色
#import <CoreBluetooth/CoreBluetooth.h> @interface BlueToothService (<CBPeripheralDelegate,CBCentralManagerDelegate> //系統藍牙設備管理對象,可以把他理解為主設備,通過他,可以去掃描和鏈接外設 @property (nonatomic, strong) CBCentralManager *centerManager; //保存掃描到的設備 @property (nonatomic, strong) NSMutableArray *peripheralArray; //當前鏈接的設備 @property (nonatomic, strong) CBPeripheral *curPeripheral; - (void)viewDidLoad { [super viewDidLoad]; //初始化並設置委托和線程隊列,最好一個線程的參數可以為nil,默認會就main線程 manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()]; }
2.掃描外設
只有當設備藍牙打開狀態下才會掃描外設,開啟掃描之前,需要先判斷藍牙狀態,這個在centeralManager成功打開的委托中。
/** * 判斷狀態 - 開始掃描 */ - (void)centralManagerDidUpdateState:(CBCentralManager *)central { switch (central.state) { case CBCentralManagerStateUnknown: NSLog(@">>>CBCentralManagerStateUnknown"); break; case CBCentralManagerStateResetting: NSLog(@">>>CBCentralManagerStateResetting"); break; case CBCentralManagerStateUnsupported: NSLog(@">>>CBCentralManagerStateUnsupported"); break; case CBCentralManagerStateUnauthorized: NSLog(@">>>CBCentralManagerStateUnauthorized"); break; case CBCentralManagerStatePoweredOff: NSLog(@">>>CBCentralManagerStatePoweredOff"); break; case CBCentralManagerStatePoweredOn: NSLog(@">>>CBCentralManagerStatePoweredOn"); //開始掃描周圍的外設 [_centerManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @NO}]; break; default: break; } }
其中 1. CBCentralManagerScanOptionAllowDuplicatesKey值為 No,表示不重復掃描已發現的設備;
2.實際掃描時,可能只需要掃描指定設備,第一個nil的參數,可以指定 CBUUID 對象的數組,即services。這時掃描只會返回正在廣告這些服務的設備。
掃描到的結果在下面方法中顯示:
/** * 掃描結果 */ - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI { NSLog(@"當掃描到設備:%@",peripheral.name); }
1.peripheral:是掃描到的設備,保存在數組中,以備后續鏈接。
2.advertisementData: 設備的廣播信息。一般硬件廠商會在廣播信息中廣播一些設備信息,如:mac地址等。可以通過硬件文檔解相關數據。
3.RSSI: 設備信號強度。這個值是波動的,硬件方應該也會給一個相應的計算方法,確定信號強度的具體程度值。
特別:在解析 advertisementData 廣播信息的時候,我遇到了一些問題:
NSData* advData = [dict objectForKey:@"kCBAdvDataManufacturerData"]; 解析出廣播信息的data格式,但是這個格式是 NSInlineData,並不是NSData格式的。在各種百度問大佬之后,終於找到了解析 的方法:具體如下:
#pragma mark 將傳入的NSData類型轉換成NSString並返回 -(NSString *)hexadecimalString:(NSData *)data{ NSString *result; const unsigned char *dataBuffer = (const unsigned char *)[data bytes]; if (!dataBuffer) { return nil; } NSUInteger dataLength = [data length]; NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; for (int i = 0; i<dataLength; i++) { //02x 表示兩個位置 顯示的16進制 [hexString appendString:[NSString stringWithFormat:@"%02lx",(unsigned long)dataBuffer[i]]]; } result = [NSString stringWithString:hexString]; return result; }
3.鏈接外設
//連接方法,掃描到的peripheral 需要保存下來,且必須持有它 [_centerManager connectPeripheral:peripheral options:nil]; //連接到Peripherals-成功 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@">>>連接到名稱為(%@)的設備-成功",peripheral.name); } //連接到Peripherals-失敗 -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@">>>連接到名稱為(%@)的設備-失敗,原因:%@",[peripheral name],[error localizedDescription]); } //Peripherals斷開連接 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@">>>外設連接斷開連接 %@: %@\n", [peripheral name], [error localizedDescription]); }
4.1掃描外設的服務
//發現服務 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { NSLog(@" service == %@ ---", peripheral.services); for (CBService *service in peripheral.services) { if ([service.UUID isEqual:[CBUUID UUIDWithString:BLUETOOTH_SERVICE_UUID]]) { //查找特征 [self.curPeripheral discoverCharacteristics:nil forService:service]; } } }
4.2.掃描外設的特征
//發現特征 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { NSLog(@"chari == %@ ---", service.characteristics); //write for (CBCharacteristic *character in service.characteristics) { //寫 CBCharacteristic if ([character.UUID isEqual:[CBUUID UUIDWithString:BLUETOOTH_CHARACTERISTIC_WRITE_UUID]]) { self.writeCharacteristic = character; } // 通知 if ([character.UUID isEqual:[CBUUID UUIDWithString:BLUETOOTH_CHARACTERISTIC_READ_UUID]]) { self.notifyCharacteristic = character; [self.curPeripheral setNotifyValue:YES forCharacteristic:character]; } } if (_connectedSuccessBlock) { _connectedSuccessBlock(peripheral,self.writeCharacteristic,self.notifyCharacteristic); } }
掃描到特征后,可以根據 上文中 提到的 硬件方 給的對應的uuid 保存獲取到相應的 CBCharacteristic;通知屬性的CBCharacteristic,需要注冊通知,才能拿到返回數據。
5.與外設做數據交互
寫數據
//寫入數據方法 [self.curPeripheral writeValue:data forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse]; //寫成功 返回方法:這個方法 只是表示 此時寫入數據成功,但並不是 硬件在寫入相應明令之后作出的正確回應,正確返回 是在 通知Characteristic 中返回的 - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { NSLog(@"char == %@ -- ", characteristic); }
//打印出 characteristic 的權限,可以看到有很多種,這是一個NS_OPTIONS,就是可以同時用於好幾個值,常見的有read,write,notify,indicate,知知道這幾個基本就夠用了,前連個是讀寫權限,后兩個都是通知,兩種不同的通知方式。 /* 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 }; */ NSLog(@"%lu", (unsigned long)characteristic.properties); //只有 characteristic.properties 有write的權限才可以寫
其實,如果硬件並沒有給出相應的uuid,也是可以 根據characteristic 的屬性,判斷其是 寫 還是通知的 特征。
6.獲取通知
一般情況下,成功寫入數據之后,比方藍牙開鎖命令,成功輸入命令之后,鎖會打開,同時,會在 監聽的通知屬性下characteristic 中,返回 開鎖成功等信息。注冊通知就是上文 掃描characteristic方法。如下:
[self.curPeripheral setNotifyValue:YES forCharacteristic:character];
監聽通知數據返回方法:
//數據更新回掉 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { NSLog(@"char == %@ -- ", characteristic); NSData *value = characteristic.value; }
拿到更新后的value 數據后,根據 硬件文檔,解析數據,便可得到正確的返回數據。
7.斷開鏈接
//停止掃描並斷開連接 -(void)disconnectPeripheral:(CBCentralManager *)centralManager peripheral:(CBPeripheral *)peripheral{ //停止掃描 [centralManager stopScan]; //斷開連接 [centralManager cancelPeripheralConnection:peripheral]; }
3.我遇到的問題
1.首先我做的是藍牙鎖硬件,總體下來感覺第一最重要的:讓硬件方給一個盡可能詳細的文檔!因為這其中不僅包括數據的解析格式,命令格式,uuid等這些,還有可能會有 加密解密算法,具體的初始密鑰或者明文秘文規定等。這些都是很重要的。
2.前面提到的在獲取廣播數據之后 NSInlineData 數據的解析,其實就是將inlinedata轉成 char* 字符格式,接着再轉換成oc的字符串。
3.byte 數組。藍牙命令格式 一般是byte數組,我對這些其實並沒有怎么接觸過。因為我們是要對命令數據 進行加密 之后再寫入。在研究了很久之后,才慢慢理清了具體的操作過程。
4,一開始在用babybluetooth,確實是走通了,但是總覺得代碼太多,因為需求比較簡單。如果需求比較復雜,或者可以直接使用babybluetooth,很全面的第三方藍牙庫。
還有,一開始有兩個問題沒有清楚導致進度特別慢:沒有寫數據要用 characteristic 的概念,還有不確定寫數據 和 寫入成功 后的數據返回在哪。在理清楚寫 characteristic,和 通知 characteristic的關系后,整個就變得很容易了。
主要是想對使用過的方法和走過的坑進行一下總結,有意見或建議可以聯系我QQ804729713。