ios:和藍(lán)牙過(guò)過(guò)招

圖片來(lái)源于網(wǎng)絡(luò)徘铝,侵權(quán)立刪

概述

公司的項(xiàng)目是醫(yī)療類的項(xiàng)目,所以這段一直在和藍(lán)牙打交道惯吕。我使用的是蘋果原生的框架CoreBluetooth惕它。在對(duì)接幾個(gè)藍(lán)牙設(shè)備的過(guò)程中,也遇到一些坑废登,下文我會(huì)一一列舉淹魄。 git上有個(gè)庫(kù)BabyBluetooth 基于原生CoreBluetooth框架進(jìn)行了封裝,使用起來(lái)也很方便钳宪,大家可以嘗試一下揭北。 那么我們開始吧!

正文

在了解下文內(nèi)容之前吏颖,我已默認(rèn)你已經(jīng)了解一些基本概念:
  • 什么是中心設(shè)備
  • 什么是外圍設(shè)備
  • 什么是服務(wù)(service)
  • 什么是特性(characteristic)
  • 什么是訂閱(notify)
  • 什么是UUID
    ...
    基本了解了以上一些概念搔体,下面的內(nèi)容將比較好理解。

**

需要注明半醉,下面的UUID是我的藍(lán)牙設(shè)備中的Service和Characteristic的UUID疚俱,要注意根據(jù)自己的藍(lán)牙
設(shè)備提供的Service和Characteristic的UUID來(lái)替換

**

// 藍(lán)牙設(shè)備提供的服務(wù)的UUID
#define kCGMServiceTwoUUID        @"0000FFF0-0000-1000-8000-00805F9B34FB"

// 藍(lán)牙設(shè)備提供的寫入特性
#define kCGMCharacteristicOneUUID @"0000FFF1-0000-1000-8000-00805F9B34FB"

// 藍(lán)牙設(shè)備提供的notify特性
#define kCGMCharacteristicTwoUUID @"0000FFF2-0000-1000-8000-00805F9B34FB"
那么,先讓我們了解下藍(lán)牙交互流程中幾個(gè)常用的回調(diào)缩多。
  • 中心設(shè)備CBCentralManager更新設(shè)備藍(lán)牙狀態(tài)的回調(diào)
  - (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    switch (central.state) {
        case CBCentralManagerStatePoweredOn:
        {
            // 掃描外圍設(shè)備
            [self.centeralManager scanForPeripheralsWithServices:nil options:nil];
        }
            break;
            
        default:
            NSLog(@"設(shè)備藍(lán)牙未開啟");
            break;
    }
}
  • 中心設(shè)備已經(jīng)發(fā)現(xiàn)外圍設(shè)備回調(diào)

**
這里有幾個(gè)問題值得注意:
**

1. 在ios中藍(lán)牙廣播信息中通常會(huì)包含以下4種類型的信息呆奕。ios的藍(lán)牙通信協(xié)議中不接受其他類型的廣播信息。因此需要注意的是衬吆,如果需要在掃描設(shè)備時(shí)梁钾,通過(guò)藍(lán)牙設(shè)備的Mac地址來(lái)唯一辨別設(shè)備,那么需要與藍(lán)牙設(shè)備的硬件工程師溝通好:將所需要的Mac地址放到一下幾種類型的廣播信息中逊抡。通常放到kCBAdvDataManufacturerData這個(gè)字段中姆泻。
kCBAdvDataIsConnectable = 1;
kCBAdvDataLocalName = XXXXXX;
kCBAdvDataManufacturerData = <XXXXXXXX>;
kCBAdvDataTxPowerLevel = 0;

2. 設(shè)備的UUID(peripheral.identifier)是由兩個(gè)設(shè)備的mac通過(guò)算法得到的,所以不同的手機(jī)連接相同的設(shè)備冒嫡,它的UUID都是不同的拇勃,無(wú)法標(biāo)識(shí)設(shè)備。

3. 蘋果與藍(lán)牙設(shè)備連接通信時(shí)孝凌,使用的并不是蘋果藍(lán)牙模塊的Mac地址方咆,使用的是蘋果隨機(jī)生成的十六進(jìn)制碼作為手機(jī)藍(lán)牙的Mac與外圍藍(lán)牙設(shè)備進(jìn)行交互。如果藍(lán)牙設(shè)備與手機(jī)在一定時(shí)間內(nèi)多次通信蟀架,那么使用的是首次連接時(shí)隨機(jī)生成的十六進(jìn)制碼作為Mac地址瓣赂,超過(guò)這個(gè)固定的時(shí)間段榆骚,手機(jī)會(huì)清空已隨機(jī)生成的Mac地址,重新生成钩述。也就是說(shuō)外圍設(shè)備是不能通過(guò)與蘋果手機(jī)的交互時(shí)所獲取的藍(lán)牙Mac地址作為手機(jī)的唯一標(biāo)識(shí)的寨躁。(這是在與寫藍(lán)牙設(shè)備的固件工程師聯(lián)調(diào)時(shí)根據(jù)問題的現(xiàn)象推測(cè)的。至于蘋果藍(lán)牙通訊協(xié)議的底層是否確實(shí)完全像我所說(shuō)的這樣牙勘,希望了解的讀者能提供幫助。在此先謝過(guò)所禀。)


 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"advertisementData.kCBAdvDataManufacturerData = %@", advertisementData[@"kCBAdvDataManufacturerData"]);
    _connectPeripheral = peripheral;
//    [self.centeralManager connectPeripheral:peripheral options:nil];
    
   if ([advertisementData[@"kCBAdvDataLocalName"] hasPrefix:@"SN"]){
        NSLog(@"已搜索到設(shè)備");
        NSLog(@"peripheral.identifier = %@  peripheral.name = %@", peripheral.identifier, peripheral.name);
        
        [_delegate getAdvertisementData:advertisementData andPeripheral:peripheral];
        
        [_peripheralArray addObject:peripheral];
    }
}
  • 中心設(shè)備設(shè)備連接成功回調(diào)
 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    // 設(shè)備停止掃描
    [self.centeralManager stopScan];
    
    peripheral.delegate = self;
    
    dispatch_after(2, dispatch_get_main_queue(), ^{
        
        // 查找服務(wù)
        [_connectPeripheral discoverServices:@[[CBUUID UUIDWithString:kCGMServiceTwoUUID]]];
    });
}
  • 中心設(shè)備設(shè)備連接失敗回調(diào)
 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    [_operationDelegate failToConnect];
}
  • 中心設(shè)備設(shè)備連接中斷回調(diào)
 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"連接斷開 %@", [error localizedDescription]);
    [_operationDelegate disconnected];
}
  • 外圍設(shè)備(CBPeripheral)發(fā)現(xiàn)服務(wù)(service)回調(diào)
  - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    if (error) {
        // 輸出錯(cuò)誤信息
        NSLog(@"discoverServices.error============ %@", [error localizedDescription]);
        
        return;
    }
    
    // 遍歷設(shè)備提供的服務(wù)
    for (CBService *service in peripheral.services) {
        NSLog(@"service.UUID = ------------- = %@", service.UUID.UUIDString);
        
        // 找到需要的服務(wù)方面,并獲取該服務(wù)響應(yīng)的特性
        if([service.UUID isEqual:[CBUUID UUIDWithString:kCGMServiceTwoUUID]]) {
            [service.peripheral discoverCharacteristics:nil forService:service];
            NSLog(@"開始查找cgm的characteristic");
        }
    }
}
  • 外圍設(shè)備發(fā)現(xiàn)特性(characteristic)回調(diào)
  - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    if (error) {
        // 輸出錯(cuò)誤信息
        NSLog(@"discoverCharacteristics.error=========== %@", [error localizedDescription]);
        return;
    }
    
    // 遍歷服務(wù)中的所有特性
    for (CBCharacteristic *characteristic in service.characteristics) {
        
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) {
            // 設(shè)置讀寫的特性
            _readAndWriteCharacteristic = characteristic;
        } else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicTwoUUID]]) {
            // 設(shè)置需要訂閱的特性
            _notifyCharacteristic = characteristic;
            [_connectPeripheral setNotifyValue:YES forCharacteristic:_notifyCharacteristic];
        }
    }
}
  • 外圍設(shè)備數(shù)據(jù)更新回調(diào), 可以在此回調(diào)方法中讀取信息(無(wú)論是read的回調(diào)色徘,還是notify(訂閱)的回調(diào)都是此方法)
  - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error) {
        // 輸出錯(cuò)誤信息
        NSLog(@"didupadteValueForCharacteristic error ============ %@", [error localizedDescription]);
        return;
    }
    NSLog(@"value ============= %@", characteristic.value);
    
    // 解析數(shù)據(jù)
    NSData *data = characteristic.value;
    
    // 將NSData轉(zhuǎn)Byte數(shù)組
    NSUInteger len = [data length];
    Byte *byteData = (Byte *)malloc(len);
    memcpy(byteData, [data bytes], len);
    NSMutableArray *commandArray = [NSMutableArray arrayWithCapacity:0];
    // Byte數(shù)組轉(zhuǎn)字符串
    for (int i = 0; i < len; i++) {
        NSString *str = [NSString stringWithFormat:@"%02x", byteData[i]];
        [commandArray addObject:str];
        NSLog(@"byteData = %@", str);
    }
    // 輸出數(shù)據(jù)
    [_operationDelegate dataWithCharacteristic:commandArray]; 
}
  • 特性已寫入外圍設(shè)備的回調(diào)(如果寫入類型為CBCharacteristicWriteWithResponse 回調(diào)此方法恭金,如果寫入類型為CBCharacteristicWriteWithoutResponse不回調(diào)此方法)
 - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error) {
        NSLog(@"write.error=======%@",error.userInfo);
    }
    
    /* When a write occurs, need to set off a re-read of the local CBCharacteristic to update its value */
    
    // 讀數(shù)據(jù)
    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) {
        [self readCharacter];
    }
}
  • 外圍設(shè)備訂閱特征值狀態(tài)改變成功的回調(diào)

需要注意的是這里是對(duì)kCGMCharacteristicOneUUID這個(gè)特性進(jìn)行寫入,這里之所以這樣操作是因?yàn)槲业乃{(lán)牙設(shè)備的藍(lán)牙協(xié)議是這樣定義的褂策,所以這里不要照抄照搬横腿,要按照你的藍(lán)牙設(shè)備的通訊協(xié)議來(lái)確定,對(duì)哪一個(gè)特性進(jìn)行read斤寂,對(duì)哪個(gè)特性進(jìn)行write耿焊,以及對(duì)哪個(gè)特性進(jìn)行設(shè)置Notify


  - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"error = %@", [error localizedDescription]);
    }
    // 對(duì)特性kCGMCharacteristicTwoUUID設(shè)置notify(訂閱),成功以后回調(diào)
    if ([characteristic.UUID.UUIDString isEqualToString:kCGMCharacteristicTwoUUID] && characteristic.isNotifying) {
        // 寫數(shù)據(jù) 回調(diào)-didWriteValueForCharacteristic
        
        NSLog(@"寫數(shù)據(jù)到cgm設(shè)備的characteristic = %@", _readAndWriteCharacteristic.UUID.UUIDString);
        [_operationDelegate writeCharacteristic];
    }
}
另外遍搞,除了回調(diào)以外罗侯,還有幾個(gè)點(diǎn)需要注意:
  • 搜索外圍設(shè)備
- (void)searchlinkDevice
{
   // 實(shí)現(xiàn)代理
   // 掃描設(shè)備
//    _centeralManager = [[CBCentralManager alloc] initWithDelegate:self
//                                                            queue:nil];
   
   if(self.centeralManager.state == CBCentralManagerStatePoweredOff) {
       // 藍(lán)牙關(guān)閉的
       
   } else if(self.centeralManager.state == CBCentralManagerStateUnsupported) {
       // 設(shè)備不支持藍(lán)牙
   } else if(self.centeralManager.state == CBCentralManagerStatePoweredOn ||
             self.centeralManager.state == CBCentralManagerStateUnknown) {
       
       // 開啟的話開始掃描藍(lán)牙設(shè)備
       [self.centeralManager scanForPeripheralsWithServices:nil options:nil];
       
       double delayInSeconds = 20.0;
       
       // 掃描20s后未掃描到設(shè)備停止掃描
       dispatch_time_t popTime =
       dispatch_time(DISPATCH_TIME_NOW,
                     (int64_t)(delayInSeconds * NSEC_PER_SEC));
       dispatch_after(popTime,
                      dispatch_get_main_queue(),
                      ^(void) {
           [self stopScan];
       });
   }
}
  • 對(duì)某個(gè)特性(characteristic)寫入數(shù)據(jù)
 - (void)writeCharacter:(NSData *)data
{
    NSLog(@" characteristic.uuid = %@ data ==== %@", _readAndWriteCharacteristic.UUID.UUIDString, data);
    if ([_readAndWriteCharacteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) {
        [_connectPeripheral writeValue:data forCharacteristic:_readAndWriteCharacteristic type:CBCharacteristicWriteWithResponse];
    } else {
        [_connectPeripheral writeValue:data
                        forCharacteristic:_readAndWriteCharacteristic
                                     type:CBCharacteristicWriteWithoutResponse];
    }
}
  • 讀數(shù)據(jù)

需要注意的是這里讀取藍(lán)牙信息 (但并不是在返回值中接收,要在

  - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

這個(gè)回調(diào)方法中接收)

 - (void)readCharacter
{
    [_connectPeripheral readValueForCharacteristic:_readAndWriteCharacteristic];
}

另外

完整的源碼請(qǐng)到這里https://github.com/xuzhengquan/BlueToothDemo/查看溪猿,demo中提供了藍(lán)牙操作的工具類钩杰,由于藍(lán)牙通訊協(xié)議的不同,所以在UI上沒有做更多的工作诊县,只是提供了搜索設(shè)備時(shí)展示了一下外圍設(shè)備信息讲弄,還請(qǐng)諒解。
文中不妥之處依痊,還請(qǐng)指正避除,謝謝。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抗悍,一起剝皮案震驚了整個(gè)濱河市驹饺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缴渊,老刑警劉巖赏壹,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異衔沼,居然都是意外死亡蝌借,警方通過(guò)查閱死者的電腦和手機(jī)昔瞧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)菩佑,“玉大人自晰,你說(shuō)我怎么就攤上這事∩耘鳎” “怎么了酬荞?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瞧哟。 經(jīng)常有香客問我混巧,道長(zhǎng),這世上最難降的妖魔是什么勤揩? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任咧党,我火速辦了婚禮,結(jié)果婚禮上陨亡,老公的妹妹穿的比我還像新娘傍衡。我一直安慰自己,他們只是感情好负蠕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布蛙埂。 她就那樣靜靜地躺著,像睡著了一般虐急。 火紅的嫁衣襯著肌膚如雪箱残。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天止吁,我揣著相機(jī)與錄音被辑,去河邊找鬼。 笑死敬惦,一個(gè)胖子當(dāng)著我的面吹牛盼理,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俄删,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼宏怔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了畴椰?” 一聲冷哼從身側(cè)響起臊诊,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎斜脂,沒想到半個(gè)月后抓艳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帚戳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年玷或,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了儡首。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偏友,死狀恐怖蔬胯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情位他,我是刑警寧澤氛濒,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站鹅髓,受9級(jí)特大地震影響泼橘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜迈勋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醋粟。 院中可真熱鬧靡菇,春花似錦、人聲如沸米愿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)育苟。三九已至较鼓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間违柏,已是汗流浹背博烂。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留漱竖,地道東北人禽篱。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像馍惹,于是被迫代替她去往敵國(guó)和親躺率。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 在寫這個(gè)博客之前万矾,空余時(shí)間抽看了近一個(gè)月的文檔和Demo悼吱,系統(tǒng)給的解釋很詳細(xì),接口也比較實(shí)用良狈,唯獨(dú)有一點(diǎn)后添,對(duì)于設(shè)備...
    木易林1閱讀 3,353評(píng)論 3 4
  • 備注:下面說(shuō)到的內(nèi)容都基于藍(lán)牙4.0標(biāo)準(zhǔn)以上,主要以實(shí)踐為主们颜。 ~ CoreBluetooth.framework...
    未_漆小七閱讀 1,617評(píng)論 1 8
  • 今天下午吕朵,我要練輪滑猎醇,我和弟弟一塊兒,結(jié)果弟弟不會(huì)滑努溃,我背著手也能滑硫嘶,看來(lái)弟弟要多加練習(xí)了。地上有好多樹枝梧税,真的是...
    mark7型閱讀 161評(píng)論 0 0
  • (文/習(xí)酒鎮(zhèn)趙半仙) 事后第队,我想作嘔哮塞。 跟一個(gè)行為檢點(diǎn)的男人酒后,在陌生的臟亂陌生女人的床上醒來(lái)的罪惡感差不多凳谦。 ...
    2632385d067a閱讀 601評(píng)論 6 20