iOS藍(lán)牙淺析

前段時(shí)間接了一個(gè)項(xiàng)目有關(guān)藍(lán)牙的颁湖,但是自己之前沒怎么接觸過藍(lán)牙俗他,就再網(wǎng)上各種search相關(guān)的文章屎媳,但是感覺都不是很具體夺溢,現(xiàn)在貼出來自己做的項(xiàng)目藍(lán)牙模塊實(shí)現(xiàn)過程,希望和大家共同學(xué)習(xí)烛谊,其實(shí)并沒有多難风响,如果你在做藍(lán)牙這一塊的話,我建議你下載一個(gè)App---LightBlue晒来。

1.需求

主要是做一個(gè)教育類App钞诡,連接藍(lán)牙進(jìn)行數(shù)據(jù)傳輸,并且對藍(lán)牙硬件進(jìn)行數(shù)據(jù)讀取和寫入湃崩。并對藍(lán)牙進(jìn)行一對一的綁定荧降,我做的這個(gè)藍(lán)牙硬件一共分為兩部分,一部分為藍(lán)牙主體(內(nèi)置藍(lán)牙硬件攒读,以下簡稱教棒)朵诫,還有一部分就是一個(gè)IC卡片(作用相當(dāng)于一個(gè)名片,我理解起來其實(shí)就是一個(gè)擁有獨(dú)立id的一個(gè)東西)薄扁。

2.實(shí)現(xiàn)功能

App連接并綁定指定的藍(lán)牙設(shè)備教棒剪返,對教棒進(jìn)行指令發(fā)送和應(yīng)答。其實(shí)藍(lán)牙設(shè)備收發(fā)數(shù)據(jù)通過的是指令邓梅,和網(wǎng)絡(luò)請求差不多:
第一步:當(dāng)然要連接到藍(lán)牙脱盲,App開啟藍(lán)牙并尋找藍(lán)牙信號(hào),截獲到藍(lán)牙設(shè)備的廣播信號(hào)(前提是藍(lán)牙要開啟狀態(tài))日缨,判斷藍(lán)牙的名字或者別的是不是自己要連接的藍(lán)牙硬件钱反,如果是直接連接。
第二步:收發(fā)數(shù)據(jù)
---App發(fā)給藍(lán)牙一個(gè)指令(一般為十六進(jìn)制的data匣距,實(shí)現(xiàn)方式就是寫數(shù)據(jù)到藍(lán)牙)面哥,該指令代表我要獲取你的系統(tǒng)地址:"嗨,藍(lán)牙把你的地址發(fā)給我"毅待。
---藍(lán)牙收到指令后智能判斷指令的含義(這部分一般藍(lán)牙硬件廠商會(huì)給出一個(gè)藍(lán)牙協(xié)議文檔尚卫,來表明各個(gè)口令和具體代表的含義),會(huì)反饋一個(gè)信息給App:我收到你的指令了尸红, 然后藍(lán)牙會(huì)繼續(xù)返回App要的數(shù)據(jù):“噢~~~吱涉,你想要我的系統(tǒng)地址是吧刹泄,拿去***這就是我的系統(tǒng)地址,你可以用這個(gè)來作為我的唯一標(biāo)識(shí)符”怎爵。
---App實(shí)現(xiàn)相應(yīng)的代碼來接收該數(shù)據(jù)循签。收到數(shù)據(jù)后一般要給藍(lán)牙一個(gè)回復(fù),有的硬件沒做這一塊:(嗨疙咸,藍(lán)牙我收到你的數(shù)據(jù)了县匠,ps:這怎么跟TCP三次握手差不多啊)和網(wǎng)絡(luò)請求一樣收到得數(shù)據(jù)一般都是data類型撒轮,App端要進(jìn)行數(shù)據(jù)解析乞旦,并作進(jìn)一步處理:存儲(chǔ)或者上傳到服務(wù)器進(jìn)行賬號(hào)藍(lán)牙綁定等。
---大體流程就是這樣题山,我寫的比較簡單明了兰粉。
建立中心角色—掃描外設(shè)(discover)—連接外設(shè)(connect)—掃描外設(shè)中的服務(wù)和特征(discover)—與外設(shè)做數(shù)據(jù)交互(explore and interact)—斷開連接(disconnect)。

3.具體實(shí)現(xiàn)細(xì)節(jié)

(1)建立中心角色(這里可以是手機(jī)也可以是藍(lán)牙硬件,我是手機(jī)作為中心角色進(jìn)行掃描藍(lán)牙信號(hào)的)顶瞳。首先在我自己類的頭文件中要包含CoreBluetooth的頭文件玖姑,并繼承兩個(gè)協(xié)議<CBCentralManagerDelegate,CBPeripheralDelegate>,代碼如下:

創(chuàng)建一個(gè)CBCentralManager成員變量作為中心
@property(nonatomic,retain)CBCentralManager * manager;
并在viewDidLoad中實(shí)例化
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

App首先要判斷手機(jī)的藍(lán)牙是否開啟

這里是Did代理函數(shù)是自動(dòng)執(zhí)行的
//藍(lán)牙狀態(tài)改變
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    NSString * message;
    switch (central.state) {
        case 0:
            message = @"初始化中慨菱,請稍后……";
            break;
        case 1:
            message = @"設(shè)備不支持狀態(tài)焰络,過會(huì)請重試……";
            break;
        case 2:
            message = @"設(shè)備未授權(quán)狀態(tài),過會(huì)請重試……";
            break;
        case 3:
            message = @"設(shè)備未授權(quán)狀態(tài)符喝,過會(huì)請重試……";
            break;
        case 4:
            message = @"尚未打開藍(lán)牙闪彼,請?jiān)谠O(shè)置中打開……";
            break;
        case 5:
            message = @"藍(lán)牙已經(jīng)成功開啟,稍后……";
            break;
        default:
            break;
    }
    if (_manager.state != CBCentralManagerStatePoweredOn ) {//如果沒有開啟提示是否開啟
        UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"開啟藍(lán)牙" message:nil delegate:self cancelButtonTitle:@"不開啟" otherButtonTitles:@"開啟", nil];
        alertView.tag = OPENBLUETOOTH;
        [alertView show];
    }else{
        //如果已經(jīng)手機(jī)開啟了藍(lán)牙协饲,那么便掃描藍(lán)牙硬件
        [self.manager scanForPeripheralsWithServices:nil options:nil];
}
}
#pragma mark - alertViewDelegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
    if (alertView.tag == OPENBLUETOOTH) {
        if (buttonIndex == 1) {
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=Bluetooth"]];
        }
    }else if (alertView.tag == ISBINDALERT){
        [self.navigationController popViewControllerAnimated:YES];
    }else if (alertView.tag == RESCANALERT){
        if (buttonIndex == 1) {
            [self.manager scanForPeripheralsWithServices:nil options:nil];
            [self initTimerAndTimeCount];
        }else{
            [self.navigationController popViewControllerAnimated:YES];
        }
    }
}
//手機(jī)藍(lán)牙發(fā)現(xiàn)了一個(gè)藍(lán)牙硬件peripheral//每發(fā)現(xiàn)一個(gè)藍(lán)牙設(shè)備都會(huì)調(diào)用此函數(shù)(如果想展示搜索到得藍(lán)牙可以逐一保存peripheral并展示)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    NSLog(@"發(fā)現(xiàn)藍(lán)牙設(shè)備:%@",peripheral.name);//
    if ([peripheral.name isEqual:藍(lán)牙的名字]) {
        self.peripheral = peripheral;
        [self.manager connectPeripheral:self.peripheral options:nil];//如果是自己要連接的藍(lán)牙硬件畏腕,那么進(jìn)行連接
    }
}
//返回的藍(lán)牙服務(wù)通知通過代理實(shí)現(xiàn)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    for (CBService * service in peripheral.services) {
        NSLog(@"Service found with UUID :%@",service.UUID);
//        if ([service.UUID isEqual:[CBUUID UUIDWithString:@"18F0"]]) {
            [peripheral discoverCharacteristics:nil forService:service];
//        }
    }
}
//查找到該設(shè)備所對應(yīng)的服務(wù)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
//每個(gè)peripheral都有很多服務(wù)service(這個(gè)依據(jù)藍(lán)牙而定),每個(gè)服務(wù)都會(huì)有幾個(gè)特征characteristic茉稠,區(qū)分這些就是UUID
//這里可以利用開頭說的LightBlue軟件連接藍(lán)牙看看你的藍(lán)牙硬件有什么服務(wù)和每個(gè)服務(wù)所包含的特征描馅,然后根據(jù)你的協(xié)議里面看看你需要用到哪個(gè)特征的哪個(gè)服務(wù)
    for (CBCharacteristic * characteristic in service.characteristics) {
//        NSLog(@"查找到的服務(wù)(屬性)%@",characteristic);
        //所對應(yīng)的屬性用于接收和發(fā)送數(shù)據(jù)
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];//監(jiān)聽這個(gè)服務(wù)發(fā)來的數(shù)據(jù)
            [peripheral readValueForCharacteristic:characteristic];//主動(dòng)去讀取這個(gè)服務(wù)發(fā)來的數(shù)據(jù)
        }
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF1"]]) {
            _characteristic = characteristic;
            //*****此處已經(jīng)連接好藍(lán)牙,可以在這里給藍(lán)牙發(fā)指令而线,也就是寫入數(shù)據(jù)
//            [self sendMessageWithType:_type];//1.查詢數(shù)量
           例:
                NSMutableData *value = [NSMutableData data];
                在這里把數(shù)據(jù)轉(zhuǎn)成data存儲(chǔ)到value里面
                NSLog(@"%@",value);
                [_peripheral writeValue:value forCharacteristic:_characteristic type:CBCharacteristicWriteWithResponse];
        }
    }
}
//接收數(shù)據(jù)的函數(shù).處理藍(lán)牙發(fā)過來得數(shù)據(jù)   讀數(shù)據(jù)代理铭污,這里已經(jīng)收到了藍(lán)牙發(fā)來的數(shù)據(jù)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error) {
        NSLog(@"Error discovering characteristics: %@", [error localizedDescription]);
        return;
    }
    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
        NSLog(@"收到藍(lán)牙發(fā)來的數(shù)據(jù)%@",characteristic.value);
        NSString * string = [self hexadecimalString:characteristic.value];
        //在這里解析收到的數(shù)據(jù),一般是data類型的數(shù)據(jù)吞获,這里要根據(jù)藍(lán)牙廠商提供的協(xié)議進(jìn)行解析并且配合LightBlue來查看數(shù)據(jù)結(jié)構(gòu)况凉,我當(dāng)時(shí)收到的數(shù)據(jù)是十六進(jìn)制的數(shù)據(jù)但是是data類型谚鄙,所以我先講data解析出來之后轉(zhuǎn)為十進(jìn)制來使用各拷。具體方法后面我會(huì)貼出
        //還有一點(diǎn)收到數(shù)據(jù)后有的硬件是需要應(yīng)答的,如果應(yīng)答的話就是在這里再給藍(lán)牙發(fā)一個(gè)指令(寫數(shù)據(jù)):“我收到發(fā)的東西了闷营,你那邊要做什么操作可以做了”烤黍。
    }
}
//*****寫數(shù)據(jù)代理知市,上面寫入數(shù)據(jù)之后就會(huì)自動(dòng)調(diào)用這個(gè)函數(shù)
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    NSLog(@"%@",characteristic.UUID);
    if (error) {
        NSLog(@"Error changing notification state: %@",[error localizedDescription]);
    }
      //其實(shí)這里貌似不用些什么(我是沒有寫只是判斷了連接狀態(tài))
}

如果要重復(fù)讀寫數(shù)據(jù),可以在每次收到數(shù)據(jù)之后發(fā)送讀取的指令速蕊,我做的項(xiàng)目就是一條一條的收數(shù)據(jù):發(fā)一個(gè)讀取指令嫂丙,返回?cái)?shù)據(jù),App做出應(yīng)答(其實(shí)就是再發(fā)一個(gè)應(yīng)答的指令)规哲,解析之后再發(fā)一條讀取指令跟啤,藍(lán)牙收到指令后刪除這個(gè)數(shù)據(jù),如此反復(fù)唉锌,直到藍(lán)牙沒有數(shù)據(jù)了隅肥。停止發(fā)送。
之前我有收到我做的項(xiàng)目還有一個(gè)IC卡袄简,藍(lán)牙可以對IC進(jìn)行讀取腥放,并生成特殊數(shù)據(jù)保存到藍(lán)牙內(nèi)存中,這個(gè)跟藍(lán)牙連接并沒有什么太大關(guān)聯(lián)绿语,我的項(xiàng)目中用到秃症,但我覺得大多數(shù)設(shè)備應(yīng)該不會(huì)用到這,就沒有詳細(xì)講吕粹。

4.我用到的相關(guān)解析函數(shù)

//將傳入的NSData類型轉(zhuǎn)換成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++){
        [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]];
    }
    result = [NSString stringWithString:hexString];
    return result;
}
//將傳入的NSString類型轉(zhuǎn)換成NSData并返回
- (NSData*)dataWithHexstring:(NSString *)hexstring{
    NSData* aData;
    return aData = [hexstring dataUsingEncoding: NSUTF16StringEncoding];
}
// 十六進(jìn)制轉(zhuǎn)換為普通字符串的种柑。
- (NSString *)stringFromHexString:(NSString *)hexString { //
    
    char *myBuffer = (char *)malloc((int)[hexString length] / 2 + 1);
    bzero(myBuffer, [hexString length] / 2 + 1);
    for (int i = 0; i < [hexString length] - 1; i += 2) {
        unsigned int anInt;
        NSString * hexCharStr = [hexString substringWithRange:NSMakeRange(i, 2)];
        NSScanner * scanner = [[NSScanner alloc] initWithString:hexCharStr];
        [scanner scanHexInt:&anInt];
        myBuffer[i / 2] = (char)anInt;
    }
    NSString *unicodeString = [NSString stringWithCString:myBuffer encoding:4];
    NSLog(@"------字符串=======%@",unicodeString);
    return unicodeString;
}
//轉(zhuǎn)換成十進(jìn)制
- (NSString *)to10:(NSString *)num
{
    NSString *result = [NSString stringWithFormat:@"%ld", strtoul([num UTF8String],0,16)];
    return result;
}
//轉(zhuǎn)換成十六進(jìn)制
- (NSString *)to16:(int)num
{
    NSString *result = [NSString stringWithFormat:@"%@",[[NSString alloc] initWithFormat:@"%1x",num]];
    if ([result length] < 2) {
        result = [NSString stringWithFormat:@"0%@", result];
    }
    return result;
    
}

5.總結(jié)

當(dāng)時(shí)我寫這個(gè)項(xiàng)目的時(shí)候各種查資料,Apple官方的demo也被我下載下來研究匹耕,好幾天不知道怎么搞莹规,也沒有仔細(xì)看廠商給的藍(lán)牙協(xié)議文檔,這里提醒大家泌神,一定要好好研究廠商給的藍(lán)牙協(xié)議文檔良漱,因?yàn)楹枚鄸|西都在那上面,沒有那個(gè)絕對做不出來欢际!第一次寫技術(shù)分享母市,寫的不好,歡迎指正损趋,希望可以幫到一些Developer患久。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浑槽,隨后出現(xiàn)的幾起案子蒋失,更是在濱河造成了極大的恐慌,老刑警劉巖桐玻,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篙挽,死亡現(xiàn)場離奇詭異,居然都是意外死亡镊靴,警方通過查閱死者的電腦和手機(jī)铣卡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門链韭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人煮落,你說我怎么就攤上這事敞峭。” “怎么了蝉仇?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵旋讹,是天一觀的道長。 經(jīng)常有香客問我轿衔,道長骗村,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任呀枢,我火速辦了婚禮胚股,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘裙秋。我一直安慰自己琅拌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布摘刑。 她就那樣靜靜地躺著进宝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枷恕。 梳的紋絲不亂的頭發(fā)上党晋,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音徐块,去河邊找鬼未玻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛胡控,可吹牛的內(nèi)容都是我干的扳剿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼昼激,長吁一口氣:“原來是場噩夢啊……” “哼庇绽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起橙困,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瞧掺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后凡傅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辟狈,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年像捶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了上陕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拓春,死狀恐怖释簿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硼莽,我是刑警寧澤庶溶,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站懂鸵,受9級(jí)特大地震影響偏螺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匆光,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一套像、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧终息,春花似錦夺巩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至续镇,卻和暖如春美澳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摸航。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工制跟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酱虎。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓凫岖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逢净。 傳聞我的和親對象是個(gè)殘疾皇子哥放,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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