藍牙通信

BLE中心模式流程-coding

BLE中心模式流程

  • 1.建立中心角色
  • 2.掃描外設(Discover Peripheral)
  • 3.連接外設(Connect Peripheral)
  • 4.掃描外設中的服務和特征(Discover Services And Characteristics)
    • 4.1 獲取外設的services
    • 4.2 獲取外設的Characteristics,獲取characteristics的值,,獲取Characteristics的Descriptor和Descriptor的值
  • 5.利用特征與外設做數(shù)據(jù)交互(Explore And Interact)
  • 6.訂閱Characteristic的通知
  • 7.斷開連接(Disconnect)

準備環(huán)境

  • 1.Xcode
  • 2.下載藍牙調試助手
  • 3.外設

實現(xiàn)步驟

#define UUID0 @"FFE0"      // 外設服務
#define UUID1 @"FFE1"       // 外設特征值(write)
#define UUID2 @"FFF2"       // 外設特征值(read)
#define UUID3 @"FFF3"       // 外設特征值(notify)
#define UUID4 @"FFF4"      // 外設特征值(掃描過濾)

導入CB頭文件,建立主設備管理類,設置主設備代理

#import <CoreBluetooth/CoreBluetooth.h>
@property (nonatomic, strong) CBCentralManager *cMgr; /**< 中心管理設備 */
- (CBCentralManager *)cMgr {
    if (!_cMgr) {
        _cMgr = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()]; // 線程不傳默認是主線程
    }
    return _cMgr;
}
  1. 設備藍牙狀態(tài)更新回調
#pragma mark 藍牙狀態(tài)更新回調
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    LogFunc
    self.peripheralState = central.state;
    switch (central.state) {
        case CBManagerStateUnknown: {
            NSLog(@"為知狀態(tài)");
        }
            break;
        case CBManagerStateResetting: {
            NSLog(@"重置狀態(tài)");
        }
            break;
        case CBManagerStateUnsupported: {
            NSLog(@"不支持的狀態(tài)");
        }
            break;
        case CBManagerStateUnauthorized: {
            NSLog(@"未授權的狀態(tài)");
        }
            break;
        case CBManagerStatePoweredOff: {
            NSLog(@"關閉狀態(tài)");
            if ([self.delegate respondsToSelector:@selector(bluetoothTool:didDisconnectPeripheral:error:)]) {
                [self.delegate bluetoothTool:self didDisconnectPeripheral:nil error:nil];
            }
            self.peripheral = nil;
            self.writeCharacteristic = nil;
            self.notifyCharacteristic = nil;
            self.readCharacteristic = nil;
        }
            break;
        case CBManagerStatePoweredOn: {
            NSLog(@"開啟狀態(tài)-可用狀態(tài)");
            self.peripheralState = central.state;
            if ([self.delegate respondsToSelector:@selector(powerOn:)]) {
                [self.delegate powerOn:self];
            }
        }
            break;
        default:
            break;
    }
}
  1. 開始掃描:
// 傳nil會掃描所有設備校辩,可以根據(jù)需求傳入你們硬件給的過濾的UDID
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
  1. 發(fā)現(xiàn)外設
#pragma mark 發(fā)現(xiàn)外設
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
 // 連接外設球订,獲取廣播數(shù)據(jù),比如mac地址等等
        self.peripheral = peripheral;
        [self.centralManager connectPeripheral:peripheral options:nil];
        NSData *adata = advertisementData[@"kCBAdvDataManufacturerData"];
        NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[adata length]];
        [adata enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
            unsigned char *dataBytes = (unsigned char*)bytes;
            for (NSInteger i = 0; i < byteRange.length; i++) {
                NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
                if ([hexStr length] == 2) {
                    [string appendString:hexStr];
                } else {
                    [string appendFormat:@"0%@", hexStr];
                }
            }
        }];
    }
}

連接成功

#pragma mark 連接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    LogFunc
    NSLog(@"連接成功");
    // 停止掃描
[self.centralManager stopScan];
    self.peripheral = peripheral;
    self.peripheral.delegate = self;
    CBUUID *uuid = [CBUUID UUIDWithString:UUID0];
    NSArray *serives = @[uuid];
    [peripheral discoverServices:serives];
}
#pragma mark 連接失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    LogFunc;
    NSLog(@"連接失敗 %@", error);
}
#pragma mark 連接斷開
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    LogFunc
    NSLog(@"連接斷開了 %@", error);
    self.peripheral = nil;
    self.writeCharacteristic = nil;
    self.notifyCharacteristic = nil;
}

3.發(fā)現(xiàn)服務

#pragma mark 發(fā)現(xiàn)服務
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    LogFunc
    // 遍歷所有的服務
    for (CBService *service in peripheral.services) {
        if ([service.UUID.UUIDString isEqualToString:UUID0]) {
            self.cbService = service;
            [self.peripheral discoverCharacteristics:nil forService:service];
            break;
        }
    }
}
  1. 發(fā)現(xiàn)特征值
#pragma mark 發(fā)現(xiàn)特征值
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    LogFunc
  for (CBCharacteristic *characteristic in service.characteristics) {
        
//        NSLog(@">>>服務:%@ 的 特征: %@,特征屬性properties:%d",service.UUID.UUIDString, characteristic.UUID.UUIDString, characteristic.properties);
        if ([characteristic.UUID.UUIDString isEqualToString:WRITE_UUID]) {
            self.writeCharacteristic = characteristic;
        } else if ([characteristic.UUID.UUIDString isEqualToString:NOTIFY_UUID]) {
            self.notifyCharacteristic = characteristic;
                //訂閱,實時接收
            if (self.notifyCharacteristic.properties & CBCharacteristicPropertyNotify) {
                [self.peripheral setNotifyValue:YES forCharacteristic:self.notifyCharacteristic];
//                [self.peripheral readValueForCharacteristic:self.notifyCharacteristic];
            }
        } else if ([characteristic.UUID.UUIDString isEqualToString:READ_UUID]) {
            self.readCharacteristic = characteristic;
            if (self.readCharacteristic.properties & CBCharacteristicPropertyRead) {
                [self.peripheral readValueForCharacteristic:self.readCharacteristic];
            }
        }
    }
// 測試寫數(shù)據(jù)
  // 藍牙重置測試
    NSString *resetString = @"00000302";
    NSData *resetData = [Tool dataForHexString:resetString];
    
//                NSData *testData = [[NSData alloc]initWithBytes:byteArray length:6];
//                NSString *hexStr = [Tool ConvertToNSString:netData];
    if (self.writeCharacteristic.properties & CBCharacteristicPropertyWrite) {
        [self.peripheral writeValue:frequencyData forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithoutResponse];
    }
}
  1. 發(fā)現(xiàn)描述descrptor 根據(jù)需求添加

  2. 接收藍牙數(shù)據(jù)

#pragma mark 接收藍牙數(shù)據(jù)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
    LogFunc;
    if (error) {
        NSLog(@"error: %@", error);
        return;
    }
    NSData *data = characteristic.value;
    NSLog(@"uuid:%@ data:%@",characteristic.UUID.UUIDString, data);
}

  1. 藍牙寫入數(shù)據(jù)回調
#pragma mark 藍牙寫入數(shù)據(jù)回調  type為CBCharacteristicWriteWithResponse回調用
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    LogFunc;
    if (error) {
    } 
}

通知回調

#pragma mark 設置通知回調
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    LogFunc
    if (error) {
        NSLog(@"error:%@", error);
        return;
    }
    if (characteristic.isNotifying) {
        [peripheral readValueForCharacteristic:characteristic];
    }
}

8.項目中遇到的坑

  • 寫給外設數(shù)據(jù)格式問題
    • 跟硬件工程師溝通,需要傳輸?shù)臄?shù)據(jù)類型,一般都是傳byte字節(jié)數(shù)組,可能需要用到的方法
#pragma mark - MD5加密
-(NSString *)md5:(NSString *)inPutText
{
    const char *cStr = [inPutText UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(cStr, strlen(cStr), result);
    
    return [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
             result[0], result[1], result[2], result[3],
             result[4], result[5], result[6], result[7],
             result[8], result[9], result[10], result[11],
             result[12], result[13], result[14], result[15]
             ] lowercaseString];
}

#pragma mark - 將NSString轉換成十六進制的字符串則可使用如下方式:
- (NSString *)convertStringToHexStr:(NSString *)str {
    if (!str || [str length] == 0) {
        return @"";
    }
    NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
    
    NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[data length]];
    
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        unsigned char *dataBytes = (unsigned char*)bytes;
        for (NSInteger i = 0; i < byteRange.length; i++) {
            NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
            if ([hexStr length] == 2) {
                [string appendString:hexStr];
            } else {
                [string appendFormat:@"0%@", hexStr];
            }
        }
    }];
    
    return string;
}
#pragma mark - 將十六進制的字符串轉換為需要的data梳毙,這就是你最終需要寫入的數(shù)據(jù):
+ (NSData *)dataWithHexString:(NSString *)hexstring
{
    NSMutableData* data = [NSMutableData data];
    int idx;
    for (idx = 0; idx+2 <= hexstring.length; idx+=2) {
        NSRange range = NSMakeRange(idx, 2);
        NSString* hexStr = [hexstring substringWithRange:range];
        NSScanner* scanner = [NSScanner scannerWithString:hexStr];
        unsigned int intValue;
        [scanner scanHexInt:&intValue];
        [data appendBytes:&intValue length:1];
    }
    return data;
}
  • 讀取數(shù)據(jù)格式問題
    • 讀取的也是byte數(shù)組
#pragma mark - 更新特征的value的時候會調用 (凡是從藍牙傳過來的數(shù)據(jù)都要經過這個回調,簡單的說這個方法就是你拿數(shù)據(jù)的唯一方法) 你可以判斷是否
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
     NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
    NSLog(@"***********");
    if (error) {
        NSLog(@"error:%@",error.localizedDescription);
        return;
    }
    NSData * data = characteristic.value;
    Byte * resultByte = (Byte *)[data bytes];
    NSLog(@"rusultByte:%s",resultByte);
    for(int i=0;i<[data length];i++){
        printf("testByteFF02[%d] = %d\n",i,resultByte[i]);
        if (resultByte[i]==0) { // 授權成功
            
            self.count++;
            self.writeFinishFlag = YES;
            break;
            
           // [self.deviceManager writeValue:[@"Lock" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }else{
            self.writeFinishFlag = NO;
        }
    }
    if (self.count==1&& self.writeFinishFlag) { // // 授權成功
        NSLog(@"授權成功");
        if (self.lockCharacteristic.properties & CBCharacteristicPropertyWrite) {
         //   [self notifyCharacteristic:peripheral characteristic:characteristic];
           // NSData *data = [NSData dataWithHexString:@"Lock"];
          NSString *lock = [self convertStringToHexStr:@"Lock"];
            NSLog(@"lock:%@",lock);
           // NSData *data = [@"Lock" dataUsingEncoding:NSUTF8StringEncoding];
            NSData *data = [NSData dataWithHexString:lock];
            NSLog(@"lockData:%@",data);
            [self.deviceManager writeValue:data forCharacteristic:self.lockCharacteristic type:CBCharacteristicWriteWithResponse];// 激活鎖
            if (characteristic.properties & CBCharacteristicPropertyRead) {
               // [peripheral readValueForCharacteristic:characteristic];
            }
            
        }
    }
    if (self.count==2&& self.writeFinishFlag) {// 激活成功
       NSLog(@"激活成功");
        if (self.lockCharacteristic.properties & CBCharacteristicPropertyWrite) {
           // [self notifyCharacteristic:peripheral characteristic:characteristic];
            NSString *unLock = [self convertStringToHexStr:@"Unlock"];
            NSData *data = [NSData dataWithHexString:unLock];
            NSLog(@"unlockData;%@",data);
            [self.deviceManager writeValue:data forCharacteristic:self.lockCharacteristic type:CBCharacteristicWriteWithResponse];// 開鎖
            if (characteristic.properties & CBCharacteristicPropertyRead) {
               // [peripheral readValueForCharacteristic:characteristic];
            }
        }
    }
    if (self.count==3&& self.writeFinishFlag) {// 開鎖成功
        NSLog(@"開鎖成功");
        [self.bleManager cancelPeripheralConnection:self.deviceManager];// 斷開鏈接
        [self.bleManager stopScan];
        self.bleManager = nil;
    }

}
//base64解密
    NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
    NSString *decodedString = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];

// 遇到的問題
藍牙調試助手測試notify捐下,要先設置notify為true才能監(jiān)聽數(shù)據(jù)讀寫
代碼也是账锹,要先拿到特征值并設置為true,才能去測試寫數(shù)據(jù)坷襟,不然代理方法監(jiān)聽不到數(shù)據(jù)讀入奸柬。
報錯:error: Error Domain=CBATTErrorDomain Code=2 "Reading is not permitted." UserInfo={NSLocalizedDescription=Reading is not permitted.}
設置notify特征值的時候不要去調入讀取數(shù)據(jù)的方法,[peripheral readValueForCharacteristic:characteristic];不然就會報錯婴程。設置notify不需要調入這個方法廓奕,只需要設置notify = true就可以了,這個方法是給讀取特征值去調入的档叔,notify不需要

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末桌粉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衙四,更是在濱河造成了極大的恐慌铃肯,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件传蹈,死亡現(xiàn)場離奇詭異押逼,居然都是意外死亡,警方通過查閱死者的電腦和手機惦界,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門挑格,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沾歪,你說我怎么就攤上這事恕齐。” “怎么了瞬逊?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵显歧,是天一觀的道長。 經常有香客問我确镊,道長士骤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任蕾域,我火速辦了婚禮拷肌,結果婚禮上到旦,老公的妹妹穿的比我還像新娘。我一直安慰自己巨缘,他們只是感情好添忘,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著若锁,像睡著了一般搁骑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上又固,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天仲器,我揣著相機與錄音,去河邊找鬼仰冠。 笑死乏冀,一個胖子當著我的面吹牛,可吹牛的內容都是我干的洋只。 我是一名探鬼主播辆沦,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼识虚!你這毒婦竟也來了众辨?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舷礼,失蹤者是張志新(化名)和其女友劉穎鹃彻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妻献,經...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡蛛株,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了育拨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谨履。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖熬丧,靈堂內的尸體忽然破棺而出笋粟,到底是詐尸還是另有隱情,我是刑警寧澤析蝴,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布害捕,位于F島的核電站,受9級特大地震影響闷畸,放射性物質發(fā)生泄漏尝盼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一佑菩、第九天 我趴在偏房一處隱蔽的房頂上張望盾沫。 院中可真熱鬧裁赠,春花似錦、人聲如沸赴精。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕾哟。三九已至一忱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渐苏,已是汗流浹背掀潮。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工菇夸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留琼富,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓庄新,卻偏偏與公主長得像鞠眉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子择诈,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內容

  • 最近學習藍牙械蹋,所以找了一些資料學習研究了一下藍牙通訊的一個流程。寫了一個小demo羞芍,demo效果見下圖: 需要de...
    NicoalsNC閱讀 1,176評論 0 7
  • 前言: 本文參考文獻:1哗戈、https://learn.adafruit.com/introduction-to-b...
    esonyf閱讀 5,248評論 3 10
  • 原文:http://www.myexception.cn/operating-system/2052286.htm...
    KYM1988閱讀 1,949評論 2 2
  • 在iOS開發(fā)中,實現(xiàn)藍牙通信有兩種方式荷科,一種是使用傳統(tǒng)的GameKit.framework唯咬,另一種就是使用在iOS...
    iOS祎閱讀 296評論 0 0
  • iOS開發(fā)之藍牙通訊 一、引言 藍牙是設備近距離通信的一種方便手段畏浆,在iPhone引入藍牙4.0后胆胰,設備之間的通訊...
    隱身人閱讀 2,392評論 3 0