最近搞了一段時間的藍牙,把一些收獲體會和大家分享一下,其實網上大神們寫的藍牙相關的都比較實用全面了,我主要是想貼一下我項目里不太一樣的地方。
藍牙的流程什么的在這里我就不贅述了,大家可以自行google。另外給大家推薦一個大牛用block封裝的藍牙---babyBlueTooth,個人感覺還是不錯的。言歸正傳,
首先,需要仔細看看硬件的說明文檔(由於本人項目硬件比較坑,文檔不詳細害的我走了很多的彎路),對藍牙的操作常用的無非就是read write 和 notify。根據一般步驟:
1,建立中心角色 2,掃描外設(discover)3,連接外設(connect) 4,掃描外設中的服務和特征(discover)(這些都是一般流程,就不再重復了),現在說一下掃描到服務和特征后的一些注意事項,一個設備里的服務和特征往往比較多,大部分情況下我們只是關心其中幾個,所以一般會在發現服務和特征的回調里去匹配我們關心那些,這些就要根據硬件的文檔具體操作了,例如我的:
for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"service:%@ 的 Characteristic: %@,characterustics的權限是什么:%lu",service.UUID,characteristic.UUID,characteristic.properties); _characteristic = characteristic; CBUUID *uid = [CBUUID UUIDWithString:@"0xFFF6"]; if ([characteristic.UUID isEqual:uid]) { [peripheral setNotifyValue:YES forCharacteristic:characteristic]; NSLog(@"開始通訊"); [manager stopScan]; //執行write } }
執行操作后就到了 5,外設做數據交互(explore and interact),數據的讀分為兩種,一種是直接讀(reading directly),另外一種是訂閱(subscribe)。從名字也能基本理解兩者的不同。實際使用中具體用一種要看具體的應用場景以及特征本身的屬性。特征有個properties字段(characteristic.properties),它是一個整型值,有如下幾個定義:
//打印出 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 }; */
注意:這個屬性也有可能是疊加的,例如,返回的characteristic.properties = 0x26,即同時有三個屬性:CBCharacteristicPropertyWriteWithoutResponse CBCharacteristicPropertyWrite CBCharacteristicPropertyIndicate
我的項目中交互的特征properties的值是0x10,表示你只能用訂閱的方式來接收數據。我這里是用訂閱的方式,啟動訂閱的代碼如下:
[_peripheral setNotifyValue:YES forCharacteristic:_readCharacteristic];
注意:這句是必須要加的,否則硬件是不會給你返回數據的。
假如propertites是訂閱,當設備有數據返回時,同樣是通過一個系統回調通知我,就會走如下方法:- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error. 這個方法是你寫入數據后就會調用的,前提是設置了代理。我在開發的過程中就遇到了這個問題,我執行write操作后這個方法不走,開始我以為是流程邏輯的問題,后來終於解決了,是因為寫入不成功,所以,假如這個方法不走的話原因就一個那就是藍牙沒有收到你發的正確的數據。因為這個函數都是以"did"開頭的,函數不用你調用,達到條件后系統后自動調用,沒有調用僅是因為沒有達到條件,當然,前提是你設置了代理。
到這里數據就能發能收了基本的需求也就OK了。
另外,我還要說一下發送數據的一些小的注意的地方,我覺得我項目中數據還是比較麻煩的,涉及的地方也比較多。藍牙接收的數據是NSData格式的,返回的也是NSData的,所以一般都要轉化成NSData的。下面是幾種格式的轉化(均為轉載,特此申明):
1,字符串轉為NSData(非編碼):
- (NSData *)convertHexStrToData:(NSString *)str { if (!str || [str length] == 0) { return nil; } NSMutableData *hexData = [[NSMutableData alloc] initWithCapacity:8]; NSRange range; if ([str length] % 2 == 0) { range = NSMakeRange(0, 2); } else { range = NSMakeRange(0, 1); } for (NSInteger i = range.location; i < [str length]; i += 2) { unsigned int anInt; NSString *hexCharStr = [str substringWithRange:range]; NSScanner *scanner = [[NSScanner alloc] initWithString:hexCharStr]; [scanner scanHexInt:&anInt]; NSData *entity = [[NSData alloc] initWithBytes:&anInt length:1]; [hexData appendData:entity]; range.location += range.length; range.length = 2; } // NSLog(@"hexdata: %@", hexData); return hexData; }
例如,NSString *modelStr = @“1311212313”;
NSLog(@“NSData = %@”,[self convertHexStrToData:modelStr]);
打印結果:
NSData = <13112123 13>,length = 5
注意,與下面的方法的對比
-(Byte)strToByte:(NSString *)str { int endMinutes = [str intValue]; Byte endMinuteByte = (Byte)0xff&endMinutes; return endMinuteByte; }
還是上面的字符串,進行下面操作
NSString *modelStr = @"1311212313"; Byte nameUser[10]; for (int i = 0; i<10;i++ ) { NSString *byteStr = [modelStr substringWithRange:NSMakeRange(i, 1)]; nameUser[i] = [self strToByte:byteStr]; } NSData *nameUserData = [NSData dataWithBytes:nameUser length:12]; NSLog(@"NSData = %@ %zd",nameUserData,nameUserData.length);
打印結果:NSData = <01030101 02010203 0103>,length = 10
上面兩種情況是轉成 相應NSData的方法,可以根據文檔提供的數據長度的要求使用。
2,求累加和(校驗和)(CHECKSUM)的求法
顧名思義,累加和即是所求數據依次累加的和,一般的規則就是前n-1個字節之和的低字節,CHECKSUM=0x100-CHECKSUM(上一步的校驗和),得到的cs字節長度1byte,類型為NSData,具體代碼如下,
- (NSData *)getCheckSum:(NSData *)byteStr{ int length = (int)byteStr.length; // NSData *data = [self hexToBytes:byteStr]; Byte *bytes = (unsigned char *)[byteStr bytes]; Byte sum = 0; for (int i = 0; i<length; i++) { sum += bytes[i]; } int sumT = sum; int at = 256 - sumT; printf("校驗和:%d\n",at); printf("累加和:%d\n",sumT); if (at == 256) { at = 0; } NSString *str = [NSString stringWithFormat:@"%@",[self ToHex:sumT]]; return [self hexToBytes:str]; } //將十進制轉化為十六進制 - (NSString *)ToHex:(int)tmpid { NSString *nLetterValue; NSString *str =@""; int ttmpig; for (int i = 0; i<9; i++) { ttmpig=tmpid%16; tmpid=tmpid/16; switch (ttmpig) { case 10: nLetterValue =@"A";break; case 11: nLetterValue =@"B";break; case 12: nLetterValue =@"C";break; case 13: nLetterValue =@"D";break; case 14: nLetterValue =@"E";break; case 15: nLetterValue =@"F";break; default: nLetterValue = [NSString stringWithFormat:@"%u",ttmpig]; } str = [nLetterValue stringByAppendingString:str]; if (tmpid == 0) { break; } } //NSLog(@"16進制是:%@",str); //不夠一個字節湊0 if(str.length == 1){ return [NSString stringWithFormat:@"0%@",str]; }else{ return str; } } - (NSData *)hexToBytes:(NSString *)str { NSMutableData* data = [NSMutableData data]; int idx; for (idx = 0; idx+2 <= str.length; idx+=2) { NSRange range = NSMakeRange(idx, 2); NSString* hexStr = [str substringWithRange:range]; NSScanner* scanner = [NSScanner scannerWithString:hexStr]; unsigned int intValue; [scanner scanHexInt:&intValue]; [data appendBytes:&intValue length:1]; } return data; }
例如,求上節中的data的累加和:
NSData *csData = [self getCheckSum:[self convertHexStrToData:modelStr]];csData即要的結果。
數據類型的轉化還有最基本的byte[]的操作,在這里就不說了。
根據BLE4.0協議,藍牙數據緩存區最大接收20字節,如果數據包過大的話,可以通過拆分成小的數據包來操作,具體的可以通過使用定時器來發送或者直接通過循環發送(在我項目中兩種我都試過了,是都可以的,但是還是那句話,還是要具體看文檔操作的),但是要注意的是,定時器的精度不是很高,(據說低於50ms就有誤差了,本人沒有實際測)。