CoreBluetooth- iOS藍牙開發(fā)

一、藍牙基礎(chǔ)知識

(一)常見簡稱

  • MFI make for ipad ,iphone, itouch 專們?yōu)樘O果設(shè)備制作的設(shè)備,開發(fā)使用ExternalAccessory 框架(認證流程貌似挺復(fù)雜的,而且對公司的資質(zhì)要求較高),詳見:關(guān)于MFi認證你所必須要知道的事情

  • BLE buletouch low energy介褥,藍牙4.0設(shè)備因為低耗電夏伊,所以也叫做BLE,開發(fā)使用CoreBluetooth 框架

(二)兩種模式

  • CBCentralMannager 中心模式 :以手機(app)作為中心房蝉,連接其他外設(shè)的場景(主要寫此種該模式的應(yīng)用方法公荧,因為我只會這種模式的)
  • CBPeripheralManager 外設(shè)模式:使用手機作為外設(shè)連接其他中心設(shè)備操作的場景

(三)CBPeripheral 带射、CBService、CBCharacteristic

三者關(guān)系:


CoreBluetooth.png

一個CBPeripheral有一個或者多個CBService循狰,而每一個CBService有一個或者多個CBCharacteristic窟社,通過可寫的CBCharacteristic發(fā)送數(shù)據(jù),而每一個CBCharacteristic有一個或者多個Description用于描述characteristic的信息或?qū)傩?/p>

(四)關(guān)于藍牙設(shè)備唯一表示的問題

在藍牙開發(fā)中券勺,iOS 沒有直接提供獲取mac 地址的方法(Android 是可以直接獲取的),所以在iOS藍牙開發(fā)中就有唯一標識的問題了

當我們使用CoreBluetooth系統(tǒng)框架進行藍牙開發(fā)的時候灿里,有時候某種功能需要和指定的藍牙設(shè)備進行操作关炼,這就需要我們拿到藍牙設(shè)備的唯一標識,來確定是哪一臺設(shè)備匣吊,先看下一當我們掃描到的藍牙設(shè)備時儒拂,所能拿到的屬性:

藍牙設(shè)備的屬性1.png
藍牙設(shè)備的屬性2.png

針對于不同的業(yè)務(wù)需求,我們在進行連接操作的時候色鸳,不要指定具體那一臺設(shè)備的社痛,那么就可以使用identifier來作為唯一標識
** 這里有一個坑要注意: 對于同一臺藍牙設(shè)備,不同手機進行掃描命雀,然后讀取的 identifier是不同的**

而對于需要指定的到藍牙設(shè)備的解決辦法:

  • 將mac 地址放在藍牙設(shè)備的廣播數(shù)據(jù)當中蒜哀,然后在廣播的時候,將mac地址一廣播的形式發(fā)出來
  • 將 mac 地址寫在某一個藍牙特征通道中吏砂,當我們連接藍牙設(shè)備之后凡怎,通過某一個特征通道獲取mac地址
  • 我們可以通過藍牙設(shè)備出廠設(shè)備 或者 后期手動修改藍牙設(shè)備的name,作為唯一標識

二赊抖、CoreBluetooth常用到的方法

自己封裝了一個藍牙單例的類,對藍牙開發(fā)中常用到的方法寫了較為詳細的解釋寨典,代碼如下:

+ (instancetype)shared {
    static SLBluetoothCentralManager *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

/**
 初始化 centralManager
 */
- (instancetype)init {
    self = [super init];
    if (self) {
        self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    }
    return self;
}

/**
 掃描周周的設(shè)備
 */
- (void) sacnNearPerpherals {
    TCLog(@"開始掃描四周的設(shè)備");
    /**
     1.第一個參數(shù)為Services的UUID(外設(shè)端的UUID) 不能為nil
     2.第二參數(shù)的CBCentralManagerScanOptionAllowDuplicatesKey為已發(fā)現(xiàn)的設(shè)備是否重復(fù)掃描氛雪,如果是同一設(shè)備會多次回調(diào)
     */
    [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @NO}];
}

#pragma mark - CBCentralManagerDelegate

/**
 檢查App設(shè)備藍牙是否可用

 @param central 中心設(shè)備管理器
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    switch (central.state){
            
        case CBCentralManagerStatePoweredOn://藍牙已打開,開始掃描外設(shè)
            [self sacnNearPerpherals];
            break;
            
        case CBCentralManagerStateUnsupported:
            [MBProgressHUD showMessage:@"您的設(shè)備不支持藍牙或藍牙4.0" toView:nil];
            break;
            
        case CBCentralManagerStateUnauthorized:
             [MBProgressHUD showMessage:@"未授權(quán)打開藍牙" toView:nil];
            break;
            
        case CBCentralManagerStatePoweredOff://藍牙未打開,系統(tǒng)會自動提示打開耸成,所以不用自行提示
            
        default:
            break;
    }
}

/**
 發(fā)現(xiàn)外圍設(shè)備的代理

 @param central 中心設(shè)備
 @param peripheral 外圍設(shè)備
 @param advertisementData 特征數(shù)據(jù)
 @param RSSI 信號強度
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    NSMutableString* nsmstring=[NSMutableString stringWithString:@"\n"];
    [nsmstring appendString:@"----發(fā)現(xiàn)外設(shè)----\n"];
    [nsmstring appendString:@"Peripheral Info:\n"];
    [nsmstring appendFormat:@"NAME: %@\n",peripheral.name];
    [nsmstring appendFormat:@"UUID(identifier): %@\n",peripheral.identifier];
    [nsmstring appendFormat:@"RSSI: %@\n",RSSI];
    [nsmstring appendFormat:@"adverisement:%@\n",advertisementData];
    TCLog(@"%@",nsmstring);
    
    NSArray *serviceUUIDArr = advertisementData[@"kCBAdvDataServiceUUIDs"];
    
    for (CBUUID *serviceUUID in serviceUUIDArr) {
        // 判斷外設(shè)是否有需要的服務(wù)(是否是當前APP對應(yīng)的外設(shè))<此項目應(yīng)該判斷外設(shè)的名字>
        if ([serviceUUID.UUIDString isEqualToString:kServiceUUID]) {
            //發(fā)現(xiàn)符合條件的周邊外設(shè)通知
            [[NSNotificationCenter defaultCenter] postNotificationName: SLDidFoundPeripheralNotification object:peripheral];
            [self connectPeripheral:peripheral];
        }
    }
}

/// 連接指定的設(shè)備
- (void)connectPeripheral:(CBPeripheral *)peripheral {
    //這個引用不可以省略
    self.peripheral = peripheral;
    TCLog(@"----嘗試連接設(shè)備----\n%@", peripheral);
    [self.centralManager connectPeripheral:peripheral
                                   options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
}

/// 連接外設(shè)成功的代理方法
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    TCLog(@"%@連接成功",peripheral.name);
    // 設(shè)置設(shè)備代理
    [self.peripheral setDelegate:self];
    [self.peripheral discoverServices:nil];
}

///連接外設(shè)失敗的代理方法
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    TCLog(@"%@連接失敗",peripheral.name);
}

///連接外設(shè)中斷的代理方法
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    TCLog(@"%@連接已斷開",peripheral.name);
}

#pragma mark - CBPeripheralDelegate
/// 獲取外設(shè)服務(wù)的代理
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    if (error) {
        TCLog(@"%@獲取服務(wù)失敗:%@",peripheral.name,error.localizedDescription);
        return;
    }
    
    for (CBService *service in peripheral.services) {
        // 找到對應(yīng)服務(wù)
        if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
            //服務(wù)中找特征
            [service.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kCharacteristicUUID]] forService:service];
        }
    }
}

///在獲取外設(shè)服務(wù)的代理的方法中如果沒有error报亩,可以調(diào)用discoverCharacteristics方法請求周邊去尋找它的服務(wù)所列出的特征,它會響應(yīng)下面的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    if (error) {
        TCLog(@"%@獲取指定特征失敗:%@",peripheral.name,error.localizedDescription);
        return;
    }
    
    NSMutableString* nsmstring=[NSMutableString stringWithString:@"\n"];
    [nsmstring appendFormat:@"------在服務(wù): %@ 中發(fā)現(xiàn) %lu 個特征 ------\n",service.UUID,(unsigned long)service.characteristics.count];
    
    for (CBCharacteristic *characteristic in service.characteristics) {
        [nsmstring appendFormat:@"%@\n",characteristic];
        [nsmstring appendFormat:@"\n"];
        TCLog(@"%@",nsmstring);
        
        self.peripheral = peripheral;
        self.writeCharacteristic = characteristic;
        // 連接成功井氢,開始配對 - 發(fā)送第一次校驗的數(shù)據(jù)
        [self willPairToPeripheral:peripheral];
    }
}

///已連接上設(shè)備,開始進行配對
- (void)willPairToPeripheral:(CBPeripheral *)peripheral{
    //發(fā)送第一次校驗的數(shù)據(jù)
    NSData *firstAuthData = [SLCheckoutDataUtils firstRandomData];
    [[SLBluetoothCentralManager shared] sendCommandData:firstAuthData];
}

/// 寫入數(shù)據(jù)方法
- (void)sendCommandData:(NSData *)data {
    if(self.writeCharacteristic.properties & CBCharacteristicPropertyWrite || self.writeCharacteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) {
        [self.peripheral writeValue:data forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];
        TCLog(@"----寫入命令---->:cmd:%@\n\n", data);
    } else {
        TCLog(@"該字段不可寫弦追!");
    }
}

/// 寫入數(shù)據(jù)后的回調(diào)方法
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"寫入失敗: %@",[error localizedDescription]);
        return;
    }
    NSLog(@"寫入成功-->%@",characteristic.value);
    [peripheral readValueForCharacteristic:characteristic];
}

/// 獲取到特征的值時回調(diào) -- 獲取回調(diào)數(shù)據(jù)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        TCLog(@"特征UUID:%@回調(diào)數(shù)據(jù)錯誤:%@", characteristic.UUID.UUIDString,error.localizedDescription);
        return;
    }
    
    TCLog(@"------特征收到回調(diào)數(shù)據(jù)------> uuid:%@ \nvalue: %@\n\n",characteristic.UUID,characteristic.value);
    NSString *result = [NSString jkt_hexStringFromData:characteristic.value];

    if ([result hasPrefix:SLDidFirstPairSuccessCommand]) {//第一次配對成功
        NSData *secondAuthData = [SLCheckoutDataUtils secondVerifyData:characteristic.value];
        [[SLBluetoothCentralManager shared] sendCommandData:secondAuthData];
    }
    
    if ([result hasPrefix:SLDidSecondPairSuccessCommand]) {//第二次配對成功
        TCLog(@"正式建立的連接-----------");
        //開鎖測試
       [self performSelector:@selector(sendOpenLockCmd) withObject:nil afterDelay:3.0];
    }
}

對于常用的方法,都寫了注釋花竞,希望對于第一藍牙開發(fā)的小伙伴有幫助>⒓!

三约急、BabyBluetooth

推薦這個庫,很??零远, 對CoreBluetooth進行了封裝,如果你不喜歡系統(tǒng)的各種代理方法厌蔽,那么可以試一試這個庫牵辣,也許你會喜歡

如果有些的不對的,不吝指教EN诚颉择浊!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逾条,隨后出現(xiàn)的幾起案子琢岩,更是在濱河造成了極大的恐慌,老刑警劉巖膳帕,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粘捎,死亡現(xiàn)場離奇詭異,居然都是意外死亡危彩,警方通過查閱死者的電腦和手機攒磨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汤徽,“玉大人娩缰,你說我怎么就攤上這事≮烁” “怎么了拼坎?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長完疫。 經(jīng)常有香客問我泰鸡,道長,這世上最難降的妖魔是什么壳鹤? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任盛龄,我火速辦了婚禮,結(jié)果婚禮上芳誓,老公的妹妹穿的比我還像新娘余舶。我一直安慰自己,他們只是感情好锹淌,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布匿值。 她就那樣靜靜地躺著,像睡著了一般赂摆。 火紅的嫁衣襯著肌膚如雪挟憔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天烟号,我揣著相機與錄音曲楚,去河邊找鬼。 笑死褥符,一個胖子當著我的面吹牛龙誊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喷楣,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼趟大,長吁一口氣:“原來是場噩夢啊……” “哼鹤树!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逊朽,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤罕伯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后叽讳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體追他,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年岛蚤,在試婚紗的時候發(fā)現(xiàn)自己被綠了邑狸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡涤妒,死狀恐怖单雾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情她紫,我是刑警寧澤硅堆,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站贿讹,受9級特大地震影響渐逃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜民褂,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一朴乖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧助赞,春花似錦、人聲如沸袁勺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽期丰。三九已至群叶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钝荡,已是汗流浹背街立。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留埠通,地道東北人赎离。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像端辱,于是被迫代替她去往敵國和親梁剔。 傳聞我的和親對象是個殘疾皇子虽画,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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