ios 蓝牙相关
?
ios藍牙開發項目實戰 -(附小米手環實例)
?前言
最近一直在開發關于藍牙的功能,本來是不想寫這一篇文章,因為網上關于ios藍牙開發的文章實在太多了,成噸成噸的文章出現,但是很遺憾都只是一些皮毛,或者只是簡單的介紹一下基本概念而已,對于一些小白可能還有很多很多疑惑,所以萌生了寫一篇文章,并附上實際例子的demo,供即將項目中準備開發的伙伴參考。
正文
首先
我們得明確一下很重要的幾個概念
1.當前ios中開發藍牙所運用的系統庫是<CoreBluetooth/CoreBluetooth.h>。
2.藍牙外設必須為4.0及以上,否則無法開發,藍牙4.0設備因為低耗電,所以也叫做BLE。
3.CoreBluetooth框架的核心其實是兩個東西,peripheral和central, 可以理解成外設和中心,就是你的蘋果手機就是中心,外部藍牙稱為外設。
4.服務和特征(service and characteristic):簡而言之,外部藍牙中它有若干個服務service(服務你可以理解為藍牙所擁有的能力),而每個服務service下擁有若干個特征characteristic(特征你可以理解為解釋這個服務的屬性)。
5.Descriptor(描述)用來描述characteristic變量的屬性。例如,一個descriptor可以規定一個可讀的描述,或者一個characteristic變量可接受的范圍,或者一個characteristic變量特定的單位。
6.跟硬件親測,Ios藍牙每次最多接收155字節的數據,安卓5.0以下最大接收20字節,5.0以上可以更改最大接收量,能達到500多字節。
其次
通過以上關鍵信息的解釋,然后看一下藍牙的開發流程:
4.1 獲取外設的services
4.2 獲取外設的Characteristics,獲取Characteristics的值,
獲取Characteristics的Descriptor和Descriptor的值
具體代碼
1.創建一個中心管理者
//只要一觸發這句代碼系統會自動檢測手機藍牙狀態,你必須實現其代理方法,當然得添加<CBCentralManagerDelegate> CBCentralManager *theManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil];//從這個代理方法中你可以看到所有的狀態,其實我們需要的只有on和off連個狀態 - (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"); break; default: break; }當發現藍牙狀態是開啟狀態,你就可以利用中央設備進行掃描外設,如果為關閉狀態,系統會自動彈出讓用戶去設置藍牙,這個不需要我們開發者關心。
2.利用中心去掃描外設
//兩個參數為nil,默認掃描所有的外設,可以設置一些服務,進行過濾搜索[theManager scanForPeripheralsWithServices:nil options:nil];2.1當掃描到外設,觸發以下代理方法
在這里需要說明的是,
一.當掃描到外設,我們可以讀到相應外設廣播信息,RSSI信號強度(可以利用RSSI計算中心和外設的距離)。
二.我們可以根據一定的規則進行連接,一般是默認名字或者名字和信號強度的規則來連接。
三.像我現在做的無鑰匙啟動車輛鎖定車輛,就需要加密通訊,不能誰來連接都可以操作車輛,需要和外設進行加密通訊,但是一切的通過算法的校驗都是在和外設連接上的基礎上進行,例如連接上了,你發送一種和硬件約定好的算法數據,硬件接收到校驗通過了就正常操作,無法通過則由硬件(外設)主動斷開。
3.當連接到外設,會調用以下代理方法
這里需要說明的是
當成功連接到外設,需要設置外設的代理,為了掃描服務調用相應代理方法
4.掃描外設中的服務和特征
//掃描到服務 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{if (error){NSLog(@"掃描外設服務出錯:%@-> %@", peripheral.name, [error localizedDescription]); return; } NSLog(@"掃描到外設服務:%@ -> %@",peripheral.name,peripheral.services); for (CBService *service in peripheral.services) { [peripheral discoverCharacteristics:nil forService:service]; } NSLog(@"開始掃描外設服務的特征 %@...",peripheral.name); } //掃描到特征 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ if (error) { NSLog(@"掃描外設的特征失敗!%@->%@-> %@",peripheral.name,service.UUID, [error localizedDescription]); return; } NSLog(@"掃描到外設服務特征有:%@->%@->%@",peripheral.name,service.UUID,service.characteristics); //獲取Characteristic的值 for (CBCharacteristic *characteristic in service.characteristics){ //這里外設需要訂閱特征的通知,否則無法收到外設發送過來的數據 [peripheral setNotifyValue:YES forCharacteristic:characteristic]; //這里以小米手環為例,當我們定義好每個特征是干什么用的,我們需要讀取這個特征的值,當特征值更新了會調用 //- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error方法 //需要說明的是UUID是硬件定義好給你,如果硬件也是個新手,那你可以先打印出所有的UUID,找出有用的 //步數 if ([characteristic.UUID.UUIDString isEqualToString:@"FF06"]) { [peripheral readValueForCharacteristic:characteristic]; } //電池電量 else if ([characteristic.UUID.UUIDString isEqualToString:@"FF0C"]) { [peripheral readValueForCharacteristic:characteristic]; } else if ([characteristic.UUID.UUIDString isEqualToString:@"2A06"]) { //震動 theSakeCC = characteristic; } } } } //掃描到具體的值->通訊主要的獲取數據的方法 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error { if (error) { NSLog(@"掃描外設的特征失敗!%@-> %@",peripheral.name, [error localizedDescription]); return; } NSLog(@"%@ %@",characteristic.UUID.UUIDString,characteristic.value); if ([characteristic.UUID.UUIDString isEqualToString:@"FF06"]) { Byte *steBytes = (Byte *)characteristic.value.bytes; int steps = bytesValueToInt(steBytes); } else if ([characteristic.UUID.UUIDString isEqualToString: @"FF0C"]) { Byte *bufferBytes = (Byte *)characteristic.value.bytes; int buterys = bytesValueToInt(bufferBytes)&0xff; NSLog(@"電池:%d%%",buterys); } else if ([characteristic.UUID.UUIDString isEqualToString:@"2A06"]) { Byte *infoByts = (Byte *)characteristic.value.bytes; //這里解析infoByts得到設備信息 } }5.與外設做數據交互
需要說明的是蘋果官方提供發送數據的方法很簡單,只需要調用下面的方法
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type我們只需要在搜索每個服務的特征,記錄這個特征,然后向這個特征發送數據就可以了。
6.斷開連接
調用以下代碼,需要說明的是中心斷開與外設的連接。
- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;以上呢是整個藍牙的開發過程,系統提供的框架api就這么多,開發起來也不是很難,要是你認為這篇文章到這里就結束了,你就大錯特錯了,這篇文章的精華內容將從這里開始,由于公司項目的保密性,我不能以它為例,那我就以小米手環為實例,主要分享一下數據解析。
精華部分
1.當調用了以下代理方法的時候,我們需要處理接收到的數據
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error小米手環所定義的幾個UUID如下:
@"FF06" 這個UUID定義的是步數
@"FF0C" 這個UUID定義的是電量
@"2A06"這個UUID定義的是震動
@"FF01"這個UUID定義的是相關的設備信息
通過以上的UUID,我們可以讀取到步數,電量,操作手環震動,并讀取手環相應設備的信息,這里需要說明的是我并不清楚設備信息的具體協議,所以這里沒法解析。
if ([characteristic.UUID.UUIDString isEqualToString:STEP]) {Byte *steBytes = (Byte *)characteristic.value.bytes; int steps = bytesValueToInt(steBytes); NSLog(@"步數:%d",steps); }當我們讀到步數這個UUID時,我們可以拿到value,小米手環所定義的協議是4個字節,我們需要將4個字節轉換為int 類型即可
方法如下
需要說明的是這個方法是C語言的方法,采用位與運算,當然如果項目中需要另一種方式的轉換,如:發過來兩字節需要你轉換為int,如果你不會轉換,可以去網上搜索,我會在文章后附一些常用的轉換方法。
這里重點說明的是步數讀取,剩余類似。
2.當我們給外設發送數據時,我們需要跟硬件定協議,當然這是在開始項目之前做好的事情。
小米手環協議中震動命令的觸發,是向硬件發送一個10進制的 2
這里需要說明的是我們發送數據給硬件一般是字節數組,然后將他轉換為NSData發送。
這里需要再添加一點,如果協議要求你發ASCII碼,例如‘SHAKE’,只需要這么處理
if (thePerpher && theSakeCC) {Byte zd[] = {'S','H','A','K','E'}; NSData *theData = [NSData dataWithBytes:zd length:1]; [thePerpher writeValue:theData forCharacteristic:theSakeCC type:CBCharacteristicWriteWithoutResponse]; }3.項目中實際開發的運用
當我們面對實際開發時,我們不可能這么直接去在一個控制器中去寫這么多代碼,如果你說這沒多少啊,那我無話可說了?。。。當然有人會說運用三方庫的啊,babyBluetooth在github上star還是挺高的,我的觀點是沒有必要去依賴所謂的三方庫,有的三方庫高度封裝性會致使我們如果遇到錯誤時無法排查,所以三方庫慎用,當然你可以參考一些Star很高的三方庫,看大神的代碼思想,有利于自己讀寫代碼的能力。
我的主要思路是封裝一個單例類,封裝好掃描的方法,讀取數據的方法(一般是代理回調),發送指令(例如小米的震動)方法,而在讀取數據中我們可以采用模型的思想,當收到藍牙外設發送過來的數據時候,我們解析完后包裝成模型通過代理回傳過去,以后我們在控制器中每次拿到的都是模型數據,這樣處理起來方便大大的。
下面將小米手環demo附上,供需要的朋友參考學習,如果文章中我有什么沒有說的很明白,或者什么疑惑可以留言。
demo https://github.com/markdashi/MIBLE
附常用轉換方法
@interface NSString (Extension)//16進制字符串轉成int - (int)convertHexStringToINT; //16進制字符串轉換為2進制的字符串 - (NSString *)getBinaryByhex; @end @implementation NSString (Extension) //不考慮內存溢出 - (int)convertHexStringToINT { UInt64 mac1 = strtoul([self UTF8String], 0, 16); return [[NSString stringWithFormat:@"%llu",mac1] intValue]; } - (NSString *)getBinaryByhex { NSMutableDictionary *hexDic = [[NSMutableDictionary alloc] init]; hexDic = [[NSMutableDictionary alloc] initWithCapacity:16]; [hexDic setObject:@"0000" forKey:@"0"]; [hexDic setObject:@"0001" forKey:@"1"]; [hexDic setObject:@"0010" forKey:@"2"]; [hexDic setObject:@"0011" forKey:@"3"]; [hexDic setObject:@"0100" forKey:@"4"]; [hexDic setObject:@"0101" forKey:@"5"]; [hexDic setObject:@"0110" forKey:@"6"]; [hexDic setObject:@"0111" forKey:@"7"]; [hexDic setObject:@"1000" forKey:@"8"]; [hexDic setObject:@"1001" forKey:@"9"]; [hexDic setObject:@"1010" forKey:@"A"]; [hexDic setObject:@"1011" forKey:@"B"]; [hexDic setObject:@"1100" forKey:@"C"]; [hexDic setObject:@"1101" forKey:@"D"]; [hexDic setObject:@"1110" forKey:@"E"]; [hexDic setObject:@"1111" forKey:@"F"]; NSMutableString *binaryString=[[NSMutableString alloc] init]; for (int i=0; i<[self length]; i++) { NSRange rage; rage.length = 1; rage.location = i; NSString *key = [self substringWithRange:rage]; binaryString = (NSMutableString *)[NSString stringWithFormat:@"%@%@",binaryString,[NSString stringWithFormat:@"%@",[hexDic objectForKey:key]]]; } return binaryString; } //NSData轉換為16進制字符串,NSData的分類 - (NSString *)dataToHexString { NSUInteger len = [self length]; char * chars = (char *)[self bytes]; NSMutableString * hexString = [[NSMutableString alloc] init]; for(NSUInteger i = 0; i < len; i++ ) [hexString appendString:[NSString stringWithFormat:@"%0.2hhx", chars[i]]]; return hexString; }2016.8.29補充
由于項目中需要做關于后臺持續掃描,類似于常見的藍牙音箱,打開手機APP連接藍牙音箱,播放音樂,當手機遠離藍牙音箱后,停止播放,當手機靠近的時候,藍牙音箱又開始播放了,對于這中需求的實現我開始很困惑,藍牙如何后臺持續掃描呢,我嘗試了很多方法是失敗的,經過我多方面查詢資料弄清楚如何實現這個需求:
1.需要后臺運行需申請后臺權限
勾選即可擁有后臺權限,如果外設持續發送數據,APP端可以接收到數據。
2.掃描時需指定serviceUUID,需外設廣播出自己的SeviceUUID,APP端作為掃描的條件。
這是蘋果掃描方法的的官方解釋:
Applications that have specified the bluetooth-central background mode are allowed to scan while backgrounded, with two
- caveats: the scan must specify one or more service types in serviceUUIDs, and the CBCentralManagerScanOptionAllowDuplicatesKey
- scan option will be ignored.
顯而易見的說的很清楚,后臺模式時藍牙作為central,必須指定serviceUUIDs,scan option忽略。
例子
掃描方法:
[self.centralManger scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:@"FEE0"],[CBUUID UUIDWithString:@"FEE7"]] options:nil];這樣當在后臺的時候是可以持續掃描的。
3.當后臺斷開連接時候會調用系統方法
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error我們需要在這里設置自動重連,即可實現上述的需求。
2016.10.19補充
關于藍牙需要做一些補充及修改,首先對于后臺掃描時候,我們申請后臺權限只需要申請
權限申請
如果兩個都申請,如果提交安裝包到APPStore,你會被拒絕
If your app is meant to work with external hardware, supported protocols must be included in the UISupportedExternalAccessoryProtocols key in your app's Info.plist file - and the hardware's PPID # should be provided in the Review Notes field of your app in iTunes Connect.
Additionally, your app must be authorized by MFi to use the desired hardware. If you are not yet in the MFi Program, you can enroll at MFi program.
Please either revise your Info.plist to include the UISupportedExternalAccessoryProtocols key and update your Review Notes to include the PPID # - or remove the external-accessory value from the UIBackgroundModes key.
轉載于:https://www.cnblogs.com/zyjzyj/p/6029968.html
總結
- 上一篇: mysql 用户管理和权限设置
- 下一篇: scikit-learn 逻辑回归类库使