iOS 蓝牙开发和注意点
前言
- 藍(lán)牙傳輸所用的框架是<CoreBluetooth/CoreBluetooth.h>
- 藍(lán)牙連接需要中心管理者和外部設(shè)備,我們所做的開發(fā)基本是圍繞中心管理來的;
- 藍(lán)牙設(shè)備發(fā)過來的每個(gè)數(shù)據(jù)包,為了保證數(shù)據(jù)在傳輸?shù)臅r(shí)候沒有丟失,一般需要包頭,包尾,校驗(yàn)和
- 有很多藍(lán)牙協(xié)議很復(fù)雜,需要把數(shù)據(jù)轉(zhuǎn)化成二進(jìn)制進(jìn)行轉(zhuǎn)化解析,對于高字節(jié),低字節(jié),小端模式,大端模式,符號(hào)位,位運(yùn)算這些基本概念需要了解清楚
1.關(guān)于Mac地址的獲取
自iOS7之后,蘋果不支持獲取Mac地址,只能用UUID來標(biāo)識(shí)設(shè)備,要注意的是同一個(gè)設(shè)備在不同手機(jī)上顯示的UUID不相同,但有的設(shè)備可以通過 “180A”這個(gè)服務(wù)來發(fā)現(xiàn)特征,再來讀取 “2A23”這個(gè)特征值,可以獲得Mac地址。如果你的藍(lán)牙設(shè)備不支持這樣獲取,你可以跟硬件工程師溝通,來獲得Mac地址,添加一個(gè)獲取地址命令或者增加一個(gè)含地址的特征值都可以很容易的獲取。上面獲取地址的前提都是需要先建立連接,如果一定要在掃描的時(shí)候獲得Mac地址,讓硬件工程師把數(shù)據(jù)寫入廣播包里,看是否可行;
2.藍(lán)牙連接流程
如果你不是新手,又不想浪費(fèi)時(shí)間,請直接看第三點(diǎn) 注意點(diǎn),核心部分
- 建立中心設(shè)備管理者
- 掃描外設(shè)
- 連接外設(shè)
- 掃描外設(shè)中的服務(wù)
- 掃描外設(shè)中的特征
- 訂閱或讀取特征值
- 獲取外設(shè)中的數(shù)據(jù)
建立中心設(shè)備管理者
// 創(chuàng)建之后會(huì)馬上檢查藍(lán)牙的狀態(tài),nil默認(rèn)為主線程 self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]藍(lán)牙線程沒必要去開異步線程,在主線程消耗不了什么性能
掃描外設(shè)
// 藍(lán)牙狀態(tài)發(fā)生改變,這個(gè)方法一定要實(shí)現(xiàn) - (void)centralManagerDidUpdateState:(CBCentralManager *)central {// 藍(lán)牙狀態(tài)可用if (central.state == CBCentralManagerStatePoweredOn) {// 如果藍(lán)牙支持后臺(tái)模式,一定要指定服務(wù),否則在后臺(tái)斷開連接不上,如果不支持,可設(shè)為nil, option里的CBCentralManagerScanOptionAllowDuplicatesKey默認(rèn)為NO, 如果設(shè)置為YES,允許搜索到重名,會(huì)很耗電[self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil];} }連接外設(shè)
/*** 發(fā)現(xiàn)設(shè)備* @param peripheral 設(shè)備* @param advertisementData 廣播內(nèi)容* @param RSSI 信號(hào)強(qiáng)度*/ - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {// 判斷是否是你需要連接的設(shè)備if ([peripheral.name isEqualToString:kPeripheralName]) {peripheral.delegate = self;// 一定要記得把外設(shè)保存起來self.selectedPeripheral = peripheral;// 開始連接設(shè)備[self.centralManager connectPeripheral:self.selectedPeripheral options:nil];} }掃描外設(shè)中的服務(wù)
/*** 已經(jīng)連接上設(shè)備*/ - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {// 停止掃描[self.centralManager stopScan];// 發(fā)現(xiàn)服務(wù)[self.selectedPeripheral discoverServices:nil]; }掃描外設(shè)中的特征
/*** 已經(jīng)發(fā)現(xiàn)服務(wù)*/ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {for (CBService *service in peripheral.services) {if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {// 根據(jù)你要的那個(gè)服務(wù)去發(fā)現(xiàn)特性[self.selectedPeripheral discoverCharacteristics:nil forService:service];}// 這里我是根據(jù) 180A 用來獲取Mac地址,沒什么實(shí)際作用,可刪掉if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]) {[self.selectedPeripheral discoverCharacteristics:nil forService:service];}} }訂閱或讀取特征值
/*** 已經(jīng)發(fā)現(xiàn)特性*/ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {for (CBCharacteristic *characteristic in service.characteristics) {if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]) {// 這里是讀取Mac地址, 可不要, 數(shù)據(jù)固定, 用readValueForCharacteristic, 不用setNotifyValue:setNotifyValue[self.selectedPeripheral readValueForCharacteristic:characteristic];}if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {// 訂閱特性,當(dāng)數(shù)據(jù)頻繁改變時(shí),一般用它, 不用readValueForCharacteristic[peripheral setNotifyValue:YES forCharacteristic:characteristic];// 獲取電池電量unsigned char send[4] = {0x5d, 0x08, 0x01, 0x3b};NSData *sendData = [NSData dataWithBytes:send length:4];// 這里的type類型有兩種 CBCharacteristicWriteWithResponse CBCharacteristicWriteWithoutResponse,它的屬性枚舉可以組合[self.selectedPeripheral writeValue:sendData forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];/*characteristic 屬性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(@"%@",characteristic);// 打印結(jié)果為 <CBCharacteristic: 0x1702a2a00, UUID = FFF6, properties = 0x16, value = (null), notifying = NO>// 我的結(jié)果 為 0x16 (0x08 & 0x16)結(jié)果不成立, (0x04 & 0x16)結(jié)果成立,那寫入類型就是 CBCharacteristicPropertyWriteWithoutResponse}} }獲取外設(shè)中的數(shù)據(jù)
/*** 數(shù)據(jù)更新的回調(diào)*/ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {// 這里收到的數(shù)據(jù)都是16進(jìn)制,有兩種轉(zhuǎn)換,一種就直接轉(zhuǎn)字符串,另一種是轉(zhuǎn)byte數(shù)組,看用哪種方便// 直接轉(zhuǎn)字符串NSString *orStr = characteristic.value.description;NSString *str = [orStr substringWithRange:NSMakeRange(1, orStr.length - 2)];NSString *dataStr = [str stringByReplacingOccurrencesOfString:@" " withString:@""];NSLog(@"dataStr = %@",dataStr);// 轉(zhuǎn)Byte數(shù)組Byte *byte = (Byte *)characteristic.value.bytes;//_______________________________________________________________________________________________________________// 解析你的協(xié)議,附幾個(gè)解協(xié)議或許能用到的函數(shù) }設(shè)備連接斷開
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {// 讓它自動(dòng)重連[self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil]; }這是系統(tǒng)代理方法,如果要主動(dòng)斷開需要調(diào)用 - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral; 這個(gè)方法
寫入數(shù)據(jù)成功的回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {// 讀取數(shù)據(jù)[self.selectedPeripheral readValueForCharacteristic:characteristic]; }如果類型是CBCharacteristicWriteWithoutResponse,不會(huì)走這個(gè)方法;
3.注意點(diǎn),核心部分,請仔細(xì)看
做藍(lán)牙前一定要去商城下個(gè)LightBlue,一個(gè)設(shè)備有很多服務(wù),服務(wù)中又有很多特性,特性中又分讀的,寫的等,有了LightBlue,你可以很快的找到你需要的特性;
LightBlue截圖
從上圖中我們可以清晰的看到每個(gè)服務(wù)中又多少個(gè)特性,特性的屬性Read、Write、Write Without Response、Notify等也標(biāo)明的很清楚,
一般的藍(lán)牙都要支持重連和后臺(tái)運(yùn)行,如果掃描設(shè)備的時(shí)候,用這個(gè)方法- (void)scanForPeripheralsWithServices:options:沒有指定特定的服務(wù),而是用nil代替,設(shè)備在后臺(tái)斷開的時(shí)候是不會(huì)重連的;
藍(lán)牙是可以同時(shí)連接多個(gè)外部設(shè)備
關(guān)于readValueForCharacteristic和 setNotifyValue:forCharacteristic: 的區(qū)別, readValueForCharacteristic適合用來讀取數(shù)據(jù)不怎么更新的特征值, 如果獲取的數(shù)據(jù)是經(jīng)常更新的,那就 一定要用setNotifyValue:forCharacteristic:來訂閱這個(gè)特征;
當(dāng)我們寫入命令時(shí)writeValue:forCharacteristic:type:,這個(gè)type類型到時(shí)是用CBCharacteristicWriteWithResponse還是用CBCharacteristicWriteWithoutResponse會(huì)有疑惑,先看一下特性屬性的枚舉,它們是可以組合的
/*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};再來看看我打印的兩個(gè)特征值,第一個(gè)是獲取Mac地址的特性,另一個(gè)是獲取數(shù)據(jù)的特性
<CBCharacteristic: 0x1700b8ae0, UUID = System ID, properties = 0x2, value = (null), notifying = NO>
<CBCharacteristic: 0x1702a2a00, UUID = FFF6, properties = 0x16, value = (null), notifying = NO>
第一個(gè)0x2對應(yīng)只可讀, 第二個(gè) (0x16 & 0x08)不成立,(0x16 & 0x04)成立,所以用CBCharacteristicWriteWithoutResponse,而且這個(gè)特征值還可讀,可以通知
代理方法- (void)centralManagerDidUpdateState:(CBCentralManager *)central;一定要調(diào)用,否則會(huì)報(bào)錯(cuò),這個(gè)方法只要設(shè)置中心設(shè)備的代理之后,就一定會(huì)走,我們最開始的掃描外設(shè)應(yīng)放在這個(gè)方法里;
對于是否要單獨(dú)創(chuàng)建一個(gè)工具類來獲取藍(lán)牙數(shù)據(jù),如果只是一個(gè)界面需要用到藍(lán)牙數(shù)據(jù),我覺得完全沒必要,如果是多個(gè)界面的話,最好還是創(chuàng)建一個(gè)工具類。
如果藍(lán)牙支持要支持后臺(tái)模式,只需要去把藍(lán)牙后臺(tái)模式打開
后臺(tái)運(yùn)行藍(lán)牙
記住只要勾選Uses Bluetooth LE accessories就行了,別勾選Acts As a Bluetooth LE accessory,除非你把你的手機(jī)當(dāng)做外部設(shè)備使用;
簡單又詳細(xì)的Demo地址
作者:alenpaulkevin
鏈接:http://www.jianshu.com/p/0ccfd53fc559
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
轉(zhuǎn)載于:https://www.cnblogs.com/Ghosgt/p/7779463.html
總結(jié)
以上是生活随笔為你收集整理的iOS 蓝牙开发和注意点的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Spring boot] Spring
- 下一篇: 【实验吧】编程循环求底运算