iOS封裝一個自己的藍(lán)牙SDK

前言

這一年,公司產(chǎn)品主要產(chǎn)品是智能鎖谓厘,兼帶接入一些其余的智能家居產(chǎn)品,lifeSmart寸谜,物聯(lián)等竟稳。幾乎是跟藍(lán)牙打了一年的交道,不寫點(diǎn)什么實(shí)在說不過去熊痴,也必須寫點(diǎn)什么證明曾經(jīng)來過住练,走你。

什么是庫愁拭?

庫是共享程序代碼的方式讲逛,一般分為靜態(tài)庫和動態(tài)庫。程序運(yùn)行分為三個步驟:編譯岭埠,鏈接盏混,執(zhí)行。編譯作用是將原代碼(程序員手寫代碼)翻譯成目標(biāo)代碼(機(jī)器的二進(jìn)制代碼)惜论,會生成目標(biāo)文件(有多少個實(shí)現(xiàn)文件就會生成多少個目標(biāo)文件)许赃。鏈接就是將各個有關(guān)聯(lián)的目標(biāo)文件和庫文件鏈接,是由機(jī)器完成的馆类,在此過程中經(jīng)常出現(xiàn)linker錯誤混聊。
1,靜態(tài)庫與動態(tài)庫

靜態(tài)庫:鏈接時完整地拷貝至可執(zhí)行文件中乾巧,被多次使用就有多份冗余拷貝句喜。
動態(tài)庫:鏈接時不復(fù)制,程序運(yùn)行時由系統(tǒng)動態(tài)加載到內(nèi)存沟于,供程序調(diào)用咳胃,系統(tǒng)只加載一次,多個程序共用旷太,節(jié)省內(nèi)存展懈。

2,iOS靜態(tài)庫形式和動態(tài)庫形式:

靜態(tài)庫:.a和.framework
動態(tài)庫:.dylib和.framework

3供璧,ramework靜態(tài)庫和動態(tài)庫的區(qū)分:

系統(tǒng)的.framework是動態(tài)庫存崖,我們自己建立的.framework是靜態(tài)庫

4,.a和.framwork的區(qū)別:

.a是一個純二進(jìn)制文件睡毒,.framework中除了有二進(jìn)制文件外還有資源文件来惧。
.a文件不能直接使用,至少要有.h文件配合吕嘀,.framework文件可以直接使用违寞。
.a + .h + sourceFile = .framework

藍(lán)牙開發(fā)基礎(chǔ)操作

1,建立中心管理者
//引用一下庫的頭文件 #import <CoreBluetooth/CoreBluetooth.h>  
//遵循<CBCentralManagerDelegate,CBPeripheralDelegate>兩個代理
CBCentralManager *manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
2偶房,掃描外設(shè)(discover)

調(diào)用1方法后趁曼,系統(tǒng)會自動檢測手機(jī)藍(lán)牙狀態(tài),并進(jìn)入此方法棕洋。當(dāng)發(fā)現(xiàn)藍(lán)牙狀態(tài)是開啟狀態(tài)挡闰,你就可以進(jìn)行下一步掃描外設(shè)操作了,如果為關(guān)閉狀態(tài)掰盘,系統(tǒng)會自動彈出讓用戶去設(shè)置藍(lán)牙摄悯,這個不需要我們開發(fā)者關(guān)心。

- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    if (central.state==CBCentralManagerStatePoweredOn) {
        //NSLog(@"藍(lán)牙已就緒,開始掃描外設(shè)");        
        //開始掃描
        [self.manager scanForPeripheralsWithServices:nil options:nil];
    }else{
        //NSLog(@"請檢查藍(lán)牙狀態(tài)"); 
    }
}
3愧捕,掃描到外設(shè)奢驯,判斷是否為目標(biāo)設(shè)備,之后進(jìn)行連接操作
//掃描到設(shè)備會進(jìn)入方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
   
    if ([peripheral.name isEqualToString:@""]) {

        self.thePerpher = peripheral;
        
        //停止掃描
        [central stopScan];
        
        //連接設(shè)備
        [central connectPeripheral:peripheral options:nil];
    }
}

這里有一個值得注意的地方次绘,安卓是可以直接掃到藍(lán)牙設(shè)備的MAC地址然后進(jìn)行連接的瘪阁,但是自從iOS7以后就無法從API直接獲取設(shè)備的MAC地址,所以絕大多數(shù)做法通常是判斷設(shè)備名字peripheral.name邮偎,像我們公司的鎖管跺,藍(lán)牙名稱一般都是dyBKMWWtwR這種,所以做法是截取頭兩位禾进,判斷是否含有“dy”字段豁跑。

4,連接設(shè)備成功泻云,開始掃描服務(wù)
//連接到外設(shè)成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
  
    //這里遵循的代理是CBPeripheralDelegate艇拍,后續(xù)的掃描服務(wù)回掉,掃描特征回掉宠纯,都是此代理里面的回掉方法 
    [peripheral setDelegate:self];
    
    //掃描服務(wù)
    [peripheral discoverServices:nil];
}

//連接外設(shè)失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"連接到外設(shè) 失斒缜恪!%@ %@",[peripheral name],[error localizedDescription]);
}
4征椒,掃描到服務(wù)(Services)娇哆,開始掃描特征(Characteristics)
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    if (error) {
        NSLog(@"掃描外設(shè)服務(wù)出錯:%@-> %@", peripheral.name, [error localizedDescription]);
        return;
    }

    //開始掃描特征
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:nil forService:service];
    }
}
5, 掃描到特征
//掃描到特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    if (error) {
        NSLog(@"掃描特征失敳取碍讨!");
        return;
    }

    //獲取Characteristic的值  
    for (CBCharacteristic *characteristic in service.characteristics){
    
        if ([characteristic.UUID.UUIDString isEqualToString:@"DE4001"]) {

            //外設(shè)訂閱特征的通知,否則無法收到外設(shè)返回給手機(jī)的數(shù)據(jù)
            [self.thePerpher setNotifyValue:YES forCharacteristic:characteristic];
      
            }else if ([characteristic.UUID.UUIDString isEqualToString:@"DE4002"]) {

                //CBCharacteristic 全局對象
                self.theSakeCC = characteristic;

            }else if ([characteristic.UUID.UUIDStringisEqualToString:@"DE4003"]) {

                //CBCharacteristic 全局對象
                self.encryptSakeCC = characteristic;
            }
        }
}

這里是一個很重要的地方蒙秒,特征值一般是硬件定義好的勃黍,比如說我這里,DE4001是回調(diào)數(shù)據(jù)read特征值(只能用來讀數(shù)據(jù))晕讲。DE4002是加密透傳傳輸Write特征值覆获,DE4003是不加密透傳傳輸Write特征值马澈,只能用來寫數(shù)據(jù)。

6弄息,給硬件發(fā)送數(shù)據(jù)
[self.thePerpher writeValue:sendData forCharacteristic:self.theSakeCC type:CBCharacteristicWriteWithoutResponse];

這里要注意在實(shí)際開發(fā)過程中痊班,根據(jù)公司的通信協(xié)議,選擇5中保存的加密或者不加密特征值發(fā)送數(shù)據(jù)摹量。

7涤伐,掃描到具體的值(硬件給App回掉數(shù)據(jù)的地方)
//掃描到具體的值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
    if (error) {
        NSLog(@"掃描特征值失敗:%@-> %@",peripheral.name, [error localizedDescription]);
        return;
    }
    //正常情況下characteristic.value需要進(jìn)行解密操作操作之后才能拿來判斷,實(shí)際開發(fā)過程中根據(jù)協(xié)議來處理
    if (characteristic.value) {
        NSString *withoutSpaceStr = [[self dataToHexStringWithData:characteristic.value] substringToIndex:4];
        if ([withoutSpaceStr isEqualToString:@"0100"] ) {
        //請輸入管理員密碼
        }
        if ([withoutSpaceStr isEqualToString:@"1101"] ) {
        //管理員密碼錯誤
        }
        if ([withoutSpaceStr isEqualToString:@"0100"] ) {
        //開鎖失敗缨称,時間誤差不在允許的范圍內(nèi)
        }
    }
}

9凝果,斷開連接(disconnect)

- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;

斷開與硬件的連接調(diào)用此方法,但是調(diào)用此方法后并不會立馬斷開睦尽,只是APP層面斷開了連接器净,物理層并沒有完全斷開,大約5s后才能完全斷開当凡,所以如果業(yè)務(wù)需求上有需要立馬斷開的掌动,建議除了調(diào)用此方法前,同時發(fā)送一個跟硬件約定的斷開命令宁玫,如果是封裝的藍(lán)牙單例類粗恢,再釋放此單例。

藍(lán)牙通訊常見加密方式

藍(lán)牙開發(fā)過程中遇到的問題匯總

1欧瘪,掃描設(shè)備時根據(jù)peripheral.name判斷目標(biāo)設(shè)備眷射,同一設(shè)備不同狀態(tài)下藍(lán)牙名稱跟更新不及時,會出現(xiàn)緩存問題

比如我們的鎖佛掖,普通狀態(tài)是dyBKMWWtwR妖碉,處于等待添加配對狀態(tài)時dyBKMWWtwA,解決辦法取廣播中的藍(lán)牙名稱芥被,實(shí)時更新不會存在緩存問題
[advertisementData objectForKey:CBAdvertisementDataLocalNameKey];

2欧宜,無論怎么調(diào)試,確認(rèn)發(fā)送數(shù)據(jù)加密拴魄,格式等都沒問題冗茸,但writeValue之后都收不到消息

我遇到這個問題的原因是跟writeValue的type有關(guān),type有兩個值匹中,CBCharacteristicWriteWithoutResponse和CBCharacteristicWriteWithResponse夏漱,這兩個值,都可以收到回調(diào)消息顶捷,之前遇到一個坑挂绰,用的是WithResponse這個type,不管怎么調(diào)試服赎,都收不到硬件返回的消息葵蒂,安卓卻可以交播,一度開始懷疑人生,百思不得其解之后践付,嘗試著換成了WithoutResponse秦士,秒收!在此大膽的猜想應(yīng)該是硬件那邊的某些設(shè)置問題荔仁。

3,拼裝的NSData數(shù)據(jù)MD5之后芽死,經(jīng)常出現(xiàn)同一數(shù)據(jù)結(jié)果不一樣

常用的md5算法如下所示乏梁,但是當(dāng)我加密一長串字節(jié)數(shù)組,比如<0213 2123 2123 1020 2123 0002>時关贵,結(jié)果會發(fā)生變化遇骑,根本原因是[data bytes]返回的結(jié)果不一樣。 解決辦法揖曾,CC_MD5方法第二個參數(shù)不用original_str的結(jié)果落萎,直接用傳入的字節(jié)數(shù)組data長度。將CC_MD5方法替換成這個CC_MD5(input, (uint)data.length, result)即可炭剪,至于為什么[data bytes]返回的結(jié)果會不一樣练链,一陣google之后尚未找到合理的解釋,有知道的同學(xué)可以科普一下奴拦。

+ (NSString*)getMD5WithData:(NSData *)data {
    const char* original_str = (const char *)[data bytes];
    unsigned char digist[CC_MD5_DIGEST_LENGTH];
    CC_MD5(original_str, (uint)strlen(original_str), digist);
    NSMutableString* outPutStr = [NSMutableString stringWithCapacity:0];
    for(int  i =0; i<CC_MD5_DIGEST_LENGTH;i++){
        [outPutStr appendFormat:@"%02x",digist[i]];
    }
    return [outPutStr lowercaseString];
}

封裝進(jìn)SDK

結(jié)語

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末媒鼓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子错妖,更是在濱河造成了極大的恐慌绿鸣,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暂氯,死亡現(xiàn)場離奇詭異潮模,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)痴施,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門擎厢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辣吃,你說我怎么就攤上這事锉矢。” “怎么了齿尽?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵沽损,是天一觀的道長。 經(jīng)常有香客問我循头,道長绵估,這世上最難降的妖魔是什么炎疆? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮国裳,結(jié)果婚禮上形入,老公的妹妹穿的比我還像新娘。我一直安慰自己缝左,他們只是感情好亿遂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著渺杉,像睡著了一般蛇数。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上是越,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天耳舅,我揣著相機(jī)與錄音,去河邊找鬼倚评。 笑死浦徊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的天梧。 我是一名探鬼主播盔性,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呢岗!你這毒婦竟也來了纯出?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤敷燎,失蹤者是張志新(化名)和其女友劉穎暂筝,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硬贯,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焕襟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了饭豹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸵赖。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拄衰,靈堂內(nèi)的尸體忽然破棺而出它褪,到底是詐尸還是另有隱情,我是刑警寧澤翘悉,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布茫打,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏老赤。R本人自食惡果不足惜轮洋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抬旺。 院中可真熱鬧弊予,春花似錦、人聲如沸开财。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽责鳍。三九已至碾褂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間薇搁,已是汗流浹背斋扰。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工渡八, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啃洋,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓屎鳍,卻偏偏與公主長得像宏娄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逮壁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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