iOS中藍(lán)牙開發(fā)

在上家公司待了2年,一直在做藍(lán)牙相關(guān)的工作,也一直沒有來總結(jié)關(guān)于藍(lán)牙的文章,昨天在交流群中的小伙伴問到藍(lán)牙相關(guān)問題,幫他看了看指出了問題,同時(shí)也發(fā)現(xiàn)很多東西長時(shí)間不看也都快忘記了,今天在這里總結(jié)一下,以防以后用時(shí)候重新找資料的煩惱,同時(shí)也希望能幫到有需要的朋友!

下面分享中 部分內(nèi)容查看了BabyBluetooth作者的分享,部分根據(jù)當(dāng)時(shí)公司的業(yè)務(wù)需求總結(jié)實(shí)戰(zhàn).希望能對(duì)有需要的人有所幫助

講解分四大模塊

  • 基礎(chǔ)知識(shí)了解
  • app作為主設(shè)備
  • app作為從設(shè)備
  • BabyBluetooth入門
  • 實(shí)戰(zhàn)OTA空中升級(jí)

一.基礎(chǔ)知識(shí)了解

藍(lán)牙4.0的設(shè)備由于耗電低,so也稱作為BLE(Bluetooth Low Energy)百度百科有關(guān)于藍(lán)牙歷史的介紹


  • peripheral  外設(shè),被連接的設(shè)備為perilheral
    
  • central   發(fā)起連接的時(shí)central
    
  • service and characteristic   服務(wù)和特征 每個(gè)設(shè)備都會(huì)提供服務(wù)和特征,類似于服務(wù)端的api,但是由于結(jié)構(gòu)不
    同,每個(gè)外設(shè)會(huì)有很多的服務(wù),每個(gè)服務(wù)中又包含很多字段,這些字段的權(quán)限一般分為 讀read,寫write,通知
    notiy幾種,就是我們連接設(shè)備后具體需要操作的內(nèi)容。
    
  • Description 每個(gè)characteristic可以對(duì)應(yīng)一個(gè)或多個(gè)Description用戶描述characteristic的信息或?qū)傩?
  • Characteristic 一個(gè)characteristic包括一個(gè)單一變量和0-n個(gè)用來描述
    characteristic變量的descriptor,characteristic可以被認(rèn)為是一個(gè)類型,類 似于類。
    
  • Descriptor Descriptor用來描述characteristic變量的屬性。例如,一個(gè)descriptor可以規(guī)定一個(gè)可讀的描述,或者一個(gè)characteristic變量可接受的
     范圍,或者一個(gè)characteristic變量特定的測(cè)量單位。
    
  •  Service service是characteristic的集合
    
* BLE中的開發(fā)要使用CoreBluetooth框架
  • CoreBluetooth框架的核心其實(shí)是兩個(gè)東西,peripheral和central, 可以理解成外設(shè)和中心。
     對(duì)應(yīng)他們分別有一組相關(guān)的API和類
    
兩種模式圖.png
  • 以上這兩組分別對(duì)應(yīng)不同的業(yè)務(wù)場(chǎng)景,左邊叫做中心模式,就是你的app作為中心,連接其他的外設(shè)的場(chǎng)景,而右邊稱為外設(shè)模式,使用手機(jī)作為
    外設(shè)別其他中心設(shè)備操作的場(chǎng)景。
    
  • 每個(gè)設(shè)備都會(huì)有一些服務(wù),每個(gè)服務(wù)里面都會(huì)有一些特征,特征就是具體鍵值對(duì),提供數(shù)據(jù)的地方。每個(gè)特征屬性分為這么幾種:讀,寫,通知
    這么幾種方式 
    
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    CBCharacteristicPropertyBroadcast                                                = 0x01,
    CBCharacteristicPropertyRead                                                    = 0x02,
    CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
    CBCharacteristicPropertyWrite                                                    = 0x08,
    CBCharacteristicPropertyNotify                                                    = 0x10,
    CBCharacteristicPropertyIndicate                                                = 0x20,
    CBCharacteristicPropertyAuthenticatedSignedWrites                                = 0x40,
    CBCharacteristicPropertyExtendedProperties                                        = 0x80,
    CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)        = 0x100,
    CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)    = 0x200
};
外設(shè)-服務(wù)-特征 之間關(guān)系圖.png
藍(lán)牙設(shè)備的幾種狀態(tài):
  1. 準(zhǔn)備(standby)
  2. 廣播(advertising)
  3. 監(jiān)聽掃描(Scanning
  4. 發(fā)起連接(Initiating)
  5. 已連接(Connected)
作為中心模式流程:
  1. 建立中心角色
  2. 掃描外設(shè)(discover)
  3. 連接外設(shè)(connect)
  4. 掃描外設(shè)中的服務(wù)和特征(discover)
    • 4.1 獲取外設(shè)的services
    • 4.2 獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值
  5. 與外設(shè)做數(shù)據(jù)交互(explore and interact)
  6. 訂閱Characteristic的通知
  7. 斷開連接(disconnect)
作為外設(shè)模式流程:
  1. 啟動(dòng)一個(gè)Peripheral管理對(duì)象
  2. 本地Peripheral設(shè)置服務(wù),特性,描述,權(quán)限等等
  3. Peripheral發(fā)送廣告
  4. 設(shè)置處理訂閱、取消訂閱、讀characteristic、寫characteristic的委托方法

部分知識(shí)參考 GATT Profile 簡(jiǎn)介 有興趣的同學(xué)可以閱讀

二. app作為主設(shè)備 掃描/連接 外設(shè) 的 相關(guān)代碼實(shí)現(xiàn)

上面說了iOS作為外設(shè)的實(shí)現(xiàn)流程:

  1. 建立中心角色
  2. 掃描外設(shè)(discover)
  3. 連接外設(shè)(connect)
  4. 掃描外設(shè)中的服務(wù)和特征(discover)
    4.1 獲取外設(shè)的services
    4.2 獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值
  5. 與外設(shè)做數(shù)據(jù)交互(explore and interact)
  6. 訂閱Characteristic的通知
  7. 斷開連接(disconnect)

下面要開始動(dòng)手寫代碼啦(??)


藍(lán)牙需要真機(jī)調(diào)試,所以必須要有真機(jī), 下載Xcode 一個(gè)藍(lán)牙外設(shè)

// 1. 導(dǎo)入 CoreBluetooth 框架,和頭文件
    // 1.1系統(tǒng)藍(lán)牙設(shè)備管理對(duì)象,可以把他理解為主設(shè)備,通過他,可以去掃描和鏈接外設(shè)
    CBCentralManager *manager;
   // 1.2 用于保存被發(fā)現(xiàn)設(shè)備
    NSMutableArray *peripherals;

  // 1.3 設(shè)置主設(shè)備的委托,CBCentralManagerDelegate
  // 1.4  必須實(shí)現(xiàn)的: //主設(shè)備狀態(tài)改變的委托,在初始化CBCentralManager的適合會(huì)打開設(shè)備,只有當(dāng)設(shè)備正確打開后才能使用
 - (void)centralManagerDidUpdateState:(CBCentralManager *)central;            //其他選擇實(shí)現(xiàn)的委托中比較重要的:找到外設(shè)的委托
 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; 
  //1.5 連接外設(shè)成功的委托
 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
//外設(shè)連接失敗的委托
 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
//斷開外設(shè)的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
 //初始化并設(shè)置委托和線程隊(duì)列,最好一個(gè)線程的參數(shù)可以為nil,默認(rèn)會(huì)就main線程
  manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];

  • 方法中 當(dāng)設(shè)備開關(guān)藍(lán)牙 都會(huì)走這個(gè)回調(diào)
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{

            switch (central.state) {
                case CBCentralManagerStateUnknown:
                    NSLog(@">>>CBCentralManagerStateUnknown");
                    break;
                case CBCentralManagerStateResetting:
                    NSLog(@">>>CBCentralManagerStateResetting");
                    break;
                case CBCentralManagerStateUnsupported:
                    NSLog(@">>>CBCentralManagerStateUnsupported");
                    break;
                case CBCentralManagerStateUnauthorized:
                    NSLog(@"CBCentralManagerStateUnauthorized");
                    break;
                case CBCentralManagerStatePoweredOff:
                    NSLog(@"CBCentralManagerStatePoweredOff");
                    break;
                case CBCentralManagerStatePoweredOn:
                    NSLog(@"CBCentralManagerStatePoweredOn");
                    //開始掃描周圍的外設(shè)
 /*
第一個(gè)參數(shù)nil就是掃描周圍所有的外設(shè),掃描到外設(shè)后會(huì)進(jìn)入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
第二個(gè)參數(shù)可以添加一些option,來增加精確的查找范圍, 如 :
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey,
                                 nil];
        [manager scanForPeripheralsWithServices:nil options:options];

 */
                    [manager scanForPeripheralsWithServices:nil options:nil];

                    break;
                default:
                    break;
            }

        }

  • 掃描到設(shè)備會(huì)進(jìn)入方法
 -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
        NSLog(@"當(dāng)掃描到設(shè)備:%@",peripheral.name);
        //接下來可以連接設(shè)備
  }
  • 連接外設(shè)(connect)
 //掃描到設(shè)備會(huì)進(jìn)入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{

//接下連接我們的測(cè)試設(shè)備,如果你沒有設(shè)備,可以下載一個(gè)app叫l(wèi)ightbule的app去模擬一個(gè)設(shè)備
//這里自己去設(shè)置下連接規(guī)則,我設(shè)置的是P開頭的設(shè)備
    if ([peripheral.name hasPrefix:@"P"]){

//  一個(gè)主設(shè)備最多能連7個(gè)外設(shè),每個(gè)外設(shè)最多只能給一個(gè)主設(shè)備連接,連接成功,失敗,斷開會(huì)進(jìn)入各自的委托
  - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設(shè)的委托

 //找到的設(shè)備必須持有它,否則CBCentralManager中也不會(huì)保存peripheral,那么CBPeripheralDelegate中的方法也不會(huì)被調(diào)用!!
         [peripherals addObject:peripheral];
                    //連接設(shè)備
         [manager connectPeripheral:peripheral options:nil];
           
   }
 }
//連接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
        {
            NSLog(@"連接到名稱為(%@)的設(shè)備-成功",peripheral.name);
        }

//連接到Peripherals-失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
        {
            NSLog(@"連接到名稱為(%@)的設(shè)備-失敗,原因:%@",[peripheral name],[error localizedDescription]);
        }

//Peripherals斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
            NSLog(@"外設(shè)連接斷開連接 %@ \n", [peripheral name]);
        }

注意 : [peripherals addObject:peripheral]; 這句代碼是要手動(dòng)持有外設(shè),必須保存!

  • 獲取外設(shè)的服務(wù)和特征
    <連接外設(shè)成功后,就要開始獲取 設(shè)備的服務(wù)了,都有相應(yīng)的方法和回調(diào),掃描到結(jié)果后會(huì)進(jìn)入delegate方法。但是這個(gè)委托已經(jīng)不再是主設(shè)備的委托(CBCentralManagerDelegate),而是外設(shè)的委托(CBPeripheralDelegate),這個(gè)委托包含了主設(shè)備與外設(shè)交互的許多 回調(diào)方法,包括獲取services,獲取characteristics,獲取characteristics的值,獲取characteristics的Descriptor,和Descriptor的值,寫數(shù)據(jù),讀rssi,用通知的方式訂閱數(shù)據(jù)等等。>
// ================== 獲取 外設(shè)的service ==============//

 //連接到Peripherals-成功回調(diào)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
        {
            NSLog(@"連接到名稱為(%@)的設(shè)備-成功",peripheral.name);
            //設(shè)置的peripheral委托CBPeripheralDelegate
            //@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
            [peripheral setDelegate:self];
            //掃描外設(shè)Services,成功后會(huì)進(jìn)入方法:
//-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{

  // 獲取service時(shí)候也可以設(shè)置option 如: 
//    NSMutableArray *serviceUUIDs = [NSMutableArray array ];
//    CBUUID *cbuuid = [CBUUID UUIDWithString:[NSString stringWithFormat:@"%x",Ble_Device_Service]];
//    [serviceUUIDs addObject:cbuuid];
//   [peripheral discoverServices:serviceUUIDs]; 
// 添加指定條件可以 提高效率

            [peripheral discoverServices:nil];

        }

        //掃描到Services
        -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
            //  NSLog(@"掃描到服務(wù):%@",peripheral.services);
            if (error)
            {
                NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
                return;
            }

            for (CBService *service in peripheral.services) {
                  NSLog(@"%@",service.UUID);
//掃描每個(gè)service的Characteristics,掃描到后會(huì)進(jìn)入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
                  [peripheral discoverCharacteristics:nil forService:service];
              }

        }


// ==========獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值============//

 //掃描到Characteristics
     -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
         if (error)
         {
             NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
             return;
         }

         for (CBCharacteristic *characteristic in service.characteristics)
         {
             NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
         }

         //獲取Characteristic的值,讀到數(shù)據(jù)會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
         for (CBCharacteristic *characteristic in service.characteristics){
             {
                 [peripheral readValueForCharacteristic:characteristic];
             }
         }

         //搜索Characteristic的Descriptors,讀到數(shù)據(jù)會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
         for (CBCharacteristic *characteristic in service.characteristics){
             [peripheral discoverDescriptorsForCharacteristic:characteristic];
         }


     }

    //獲取的charateristic的值
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
        //打印出characteristic的UUID和值
        //!注意,value的類型是NSData,具體開發(fā)時(shí),會(huì)根據(jù)外設(shè)協(xié)議制定的方式去解析數(shù)據(jù)
        NSLog(@"characteristic uuid:%@  value:%@",characteristic.UUID,characteristic.value);

    }

    //搜索到Characteristic的Descriptors
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{

        //打印出Characteristic和他的Descriptors
         NSLog(@"characteristic uuid:%@",characteristic.UUID);
        for (CBDescriptor *d in characteristic.descriptors) {
            NSLog(@"Descriptor uuid:%@",d.UUID);
        }

    }
    //獲取到Descriptors的值
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
        //打印出DescriptorsUUID 和value
        //這個(gè)descriptor都是對(duì)于characteristic的描述,一般都是字符串,所以這里我們轉(zhuǎn)換成字符串去解析
        NSLog(@"characteristic uuid:%@  value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
    }

  • 把數(shù)據(jù)寫到 Characteristic 中
//寫數(shù)據(jù)
    -(void)writeCharacteristic:(CBPeripheral *)peripheral
                characteristic:(CBCharacteristic *)characteristic
                         value:(NSData *)value{

        //打印出 characteristic 的權(quán)限,可以看到有很多種,這是一個(gè)NS_OPTIONS,就是可以同時(shí)用于好幾個(gè)值,常見的有read,write,notify,indicate,知知道這幾個(gè)基本就夠用了,前連個(gè)是讀寫權(quán)限,后兩個(gè)都是通知,兩種不同的通知方式。
        /*
         typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
         CBCharacteristicPropertyBroadcast                                              = 0x01,
         CBCharacteristicPropertyRead                                                   = 0x02,
         CBCharacteristicPropertyWriteWithoutResponse                                   = 0x04,
         CBCharacteristicPropertyWrite                                                  = 0x08,
         CBCharacteristicPropertyNotify                                                 = 0x10,
         CBCharacteristicPropertyIndicate                                               = 0x20,
         CBCharacteristicPropertyAuthenticatedSignedWrites                              = 0x40,
         CBCharacteristicPropertyExtendedProperties                                     = 0x80,
         CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)        = 0x100,
         CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)  = 0x200
         };

         */
        NSLog(@"%lu", (unsigned long)characteristic.properties);

        //只有 characteristic.properties 有write的權(quán)限才可以寫
        if(characteristic.properties & CBCharacteristicPropertyWrite){
            /*
                最好一個(gè)type參數(shù)可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區(qū)別是是否會(huì)有反饋
            */
            [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }else{
            NSLog(@"該字段不能寫!");
        }
    }

  • 訂閱Characteristic的通知
 //設(shè)置通知
    -(void)notifyCharacteristic:(CBPeripheral *)peripheral
                characteristic:(CBCharacteristic *)characteristic{
        //設(shè)置通知,數(shù)據(jù)通知會(huì)進(jìn)入:didUpdateValueForCharacteristic方法
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];

    }

    //取消通知
    -(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
                 characteristic:(CBCharacteristic *)characteristic{

         [peripheral setNotifyValue:NO forCharacteristic:characteristic];
    }

  • 斷開連接
//停止掃描并斷開連接
    -(void)disconnectPeripheral:(CBCentralManager *)centralManager
                     peripheral:(CBPeripheral *)peripheral{
        //停止掃描
        [centralManager stopScan];
        //斷開連接
        [centralManager cancelPeripheralConnection:peripheral];
    }

三. app作為外設(shè)被主設(shè)備連接

這里使用app作為一個(gè)peripheral,被其它的central連接

peripheral連接流程:
  1. 打開peripheralManager,設(shè)置peripheralManager的delegate
  2. 創(chuàng)建characteristics,characteristics的description 創(chuàng)建service,把characteristics添加到service中,再把service添加到peripheralManager中
  3. 開啟廣播advertising
  4. 對(duì)central的操作進(jìn)行響應(yīng)
    • 4.1 讀characteristics請(qǐng)求
    • 4.2 寫characteristics請(qǐng)求
    • 4.4 訂閱和取消訂閱characteristics
具體步驟核心代碼:
  1. <打開peripheralManager,設(shè)置peripheralManager的委托>
  2. <初始化peripheralManager, 這里的peripheralManager和CBCentralManager 很相似>
    peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
  1. <創(chuàng)建characteristics 創(chuàng)建service,把characteristics添加到service 然后把service添加到peripheralManager>
 - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
  // 在這里判斷藍(lán)牙的狀態(tài), 因?yàn)樗{(lán)牙打開成功后才能配置service和characteristics
    [self config];
}
  1. <配置相關(guān)service和>
//配置藍(lán)牙的服務(wù)和特征
 -(void)config{

        //特征字段描述
        CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];

        /*
         可以通知的Characteristic
         properties:CBCharacteristicPropertyNotify
         permissions CBAttributePermissionsReadable
         */
        CBMutableCharacteristic *notiyCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:notiyCharacteristicUUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

        /*
         可讀寫的characteristics
         properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead
         permissions CBAttributePermissionsReadable | CBAttributePermissionsWriteable
         */
        CBMutableCharacteristic *readwriteCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readwriteCharacteristicUUID] properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
        //設(shè)置description
        CBMutableDescriptor *readwriteCharacteristicDescription1 = [[CBMutableDescriptor alloc]initWithType: CBUUIDCharacteristicUserDescriptionStringUUID value:@"name"];
        [readwriteCharacteristic setDescriptors:@[readwriteCharacteristicDescription1]];
        /*
         只讀的Characteristic
         properties:CBCharacteristicPropertyRead
         permissions CBAttributePermissionsReadable
         */
        CBMutableCharacteristic *readCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readCharacteristicUUID] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];

        //service1初始化并加入兩個(gè)characteristics
        CBMutableService *service1 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];
        [service1 setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];

        //service2初始化并加入一個(gè)characteristics
        CBMutableService *service2 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID2] primary:YES];
        [service2 setCharacteristics:@[readCharacteristic]];

        //添加后就會(huì)調(diào)用代理的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
        [peripheralManager addService:service1];
        [peripheralManager addService:service2];
 }
  1. <開啟廣播>
//perihpheral添加了service
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
    if (error == nil) {
        serviceNum++;
    }
    //因?yàn)槲覀兲砑恿?個(gè)服務(wù),所以想兩次都添加完成后才去發(fā)送廣播
    if (serviceNum==2) {
        //添加服務(wù)后可以在此向外界發(fā)出通告 調(diào)用完這個(gè)方法后會(huì)調(diào)用代理的
        //(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
        [peripheralManager startAdvertising:@{
                                              CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:ServiceUUID1],[CBUUID UUIDWithString:ServiceUUID2]],
                                              CBAdvertisementDataLocalNameKey : LocalNameKey
                                             }
         ];

    }

}
//peripheral開始發(fā)送advertising
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
    NSLog(@"in peripheralManagerDidStartAdvertisiong");
}

  1. <對(duì)central的操作進(jìn)行響應(yīng)>
  • 1 讀characteristics請(qǐng)求
    2 寫characteristics請(qǐng)求
    3 訂閱和取消訂閱characteristics
    
//訂閱characteristics
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
    NSLog(@"訂閱了 %@的數(shù)據(jù)",characteristic.UUID);
    //每秒執(zhí)行一次給主設(shè)備發(fā)送一個(gè)當(dāng)前時(shí)間的秒數(shù)
    timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendData:) userInfo:characteristic  repeats:YES];
}

//取消訂閱characteristics
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
    NSLog(@"取消訂閱 %@的數(shù)據(jù)",characteristic.UUID);
    //取消回應(yīng)
    [timer invalidate];
}

//發(fā)送數(shù)據(jù),發(fā)送當(dāng)前時(shí)間的秒數(shù)
-(BOOL)sendData:(NSTimer *)t {
    CBMutableCharacteristic *characteristic = t.userInfo;
    NSDateFormatter *dft = [[NSDateFormatter alloc]init];
    [dft setDateFormat:@"ss"];
    NSLog(@"%@",[dft stringFromDate:[NSDate date]]);

    //執(zhí)行回應(yīng)Central通知數(shù)據(jù)
    return  [peripheralManager updateValue:[[dft stringFromDate:[NSDate date]] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:nil];

}


//讀characteristics請(qǐng)求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
    NSLog(@"didReceiveReadRequest");
    //判斷是否有讀數(shù)據(jù)的權(quán)限
    if (request.characteristic.properties & CBCharacteristicPropertyRead) {
        NSData *data = request.characteristic.value;
        [request setValue:data];
        //對(duì)請(qǐng)求作出成功響應(yīng)
        [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }else{
        [peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
    }
}


//寫characteristics請(qǐng)求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
    NSLog(@"didReceiveWriteRequests");
    CBATTRequest *request = requests[0];

    //判斷是否有寫數(shù)據(jù)的權(quán)限
    if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
        //需要轉(zhuǎn)換成CBMutableCharacteristic對(duì)象才能進(jìn)行寫值
        CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;
        c.value = request.value;
        [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }else{
        [peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
    }

}

四. 開源庫 BabyBluetooth介紹

有需要的同學(xué)可以看看BabyBluetooth源碼

GitHub上BabyBluetooth源碼

BabyBluetooth的優(yōu)缺點(diǎn)
BabyBluetooth使用block方法,可以一定程度上節(jié)省開發(fā)時(shí)間,節(jié)約成本,畢竟直接用官方的框架寫還是比較麻煩的,
當(dāng)然這還要根據(jù)自己的實(shí)際情況來,當(dāng)時(shí)由于我所在公司需要自己的SDK,根據(jù)當(dāng)時(shí)的業(yè)務(wù)情況,只能我自己來寫了,
而BabyBluetooth鏈?zhǔn)降恼Z法寫起來比較清爽. 如果公司有一些業(yè)務(wù)上的需求BabyBluetooth沒有實(shí)現(xiàn),
等待作者更新是不太現(xiàn)實(shí)的,這時(shí)候自己來動(dòng)手開發(fā)一個(gè)吧!
使用步驟可以參考 :

iOS藍(lán)牙篇之BabyBluetooth詳解
iOS藍(lán)牙開發(fā):解析BabyBluetooth
iOS 藍(lán)牙庫BabyBluetooth的使用筆記

五. OTA(空中升級(jí)實(shí)戰(zhàn))

當(dāng)時(shí)的業(yè)務(wù)需求是 :

  • 讀取地鎖當(dāng)前硬件的版本號(hào),判斷是否需要升級(jí)
    
  • 如果需要升級(jí),手機(jī)連接地鎖后給地鎖傳輸腳本給地鎖中的硬件進(jìn)行升級(jí)
    

<讀取硬件版本號(hào) 要連接上外設(shè)后 讀取的characteristic的值>

在回調(diào)方法 : 
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
中處理
版本號(hào).png
  • 空中升級(jí)
    這里會(huì)借助一個(gè)第三方的開源庫 : Nordic GitHub
DFUFirmware *selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:self.filePath];
 self.initiator = [[DFUServiceInitiator alloc] initWithCentralManager: self.centralManager target:self.peripheral];
 [self.initiator withFirmwareFile:selectedFirmware];
 self.initiator.delegate = self; // - to be informed about current state and errors
 self.initiator.logger = self;
 self.initiator.progressDelegate = self;
 self.dfuController = [self.initiator start];

??需要的代理:

#pragma mark DFUServiceDelegate
- (void)onUploadProgress:(NSInteger)part totalParts:(NSInteger)totalParts progress:(NSInteger)progress currentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond{
    
    NSLog(@" %s   %.2ld",__func__,(long)progress);
    
    [self.delegate onFSMOtaUpgradeProgress:progress];
}

- (void)didErrorOccur:(enum DFUError)error withMessage:(NSString *)message{
    
    switch (error) {
        case DFUErrorRemoteSuccess:
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteSuccess"]];
            break;
        case DFUErrorRemoteInvalidState :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteInvalidState"]];
            break;
        case DFUErrorRemoteNotSupported :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteNotSupported"]];
            break;
        case DFUErrorRemoteDataExceedsLimit :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteDataExceedsLimit"]];
            break;
        case DFUErrorRemoteCrcError :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteCrcError"]];
            break;
        case DFUErrorRemoteOperationFailed:
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteOperationFailed"]];
            break;
        case DFUErrorFileNotSpecified:
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileNotSpecified"]];
            break;
        case DFUErrorFileInvalid :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileInvalid"]];
            break;
        case DFUErrorExtendedInitPacketRequired :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorExtendedInitPacketRequired"]];
            break;
        case DFUErrorInitPacketRequired :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorInitPacketRequired"]];
            break;
        case DFUErrorFailedToConnect :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFailedToConnect"]];
            break;
        case DFUErrorDeviceDisconnected:
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceDisconnected"]];
            break;
        case DFUErrorServiceDiscoveryFailed:
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorServiceDiscoveryFailed"]];
            break;
        case DFUErrorDeviceNotSupported :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceNotSupported"]];
            break;
        case DFUErrorReadingVersionFailed :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReadingVersionFailed"]];
            break;
        case DFUErrorEnablingControlPointFailed :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorEnablingControlPointFailed"]];
            break;
        case DFUErrorWritingCharacteristicFailed :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorWritingCharacteristicFailed"]];
            break;
        case DFUErrorReceivingNotificationFailed :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReceivingNotificationFailed"]];
            break;
        case DFUErrorUnsupportedResponse :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorUnsupportedResponse"]];
            break;
        case DFUErrorBytesLost :
            [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorBytesLost"]];
            break;
        default:
            break;
    }
    
    
    NSLog(@" %s 升級(jí)失敗  錯(cuò)誤的信息是: %@",__func__ ,message);
    
    NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject];
    
    [savePath  stringByAppendingPathComponent:@"ble_app_wechat_debug_v1.1.zip"];

    [self disConnectDevice:self.lockId];
    
    NSError *err;
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    [fileMgr removeItemAtPath:savePath error:&err];

    
}

#pragma mark LoggerDelegate 代理
- (void)logWith:(enum LogLevel)level message:(NSString * _Nonnull)message{
    
//    NSLog(@"消息是 : %@",message);
}

- (void)didStateChangedTo:(enum DFUState)state{
    
    switch (state) {
        case DFUStateAborted:{
            [self otaCallback:@"DFUStateAborted"];
            break;
        }
        case DFUStateConnecting:{
            [self otaCallback:@"DFUStateConnecting"];
            break;
        }
        case DFUStateDisconnecting:{
            [self otaCallback:@"DFUStateDisconnecting"];
            break;
        }
        case DFUStateEnablingDfuMode:{
            [self otaCallback:@"DFUStateEnablingDfuMode"];
            break;
        }
        case DFUStateStarting:{
            [self otaCallback:@"DFUStateStarting"];
            break;
        }
        case DFUStateUploading:{
            [self otaCallback:@"DFUStateUploading"];
            break;
        }
        case DFUStateValidating:{
            [self otaCallback:@"DFUStateValidating"];
            break;
        }
        case DFUStateCompleted:{
            [self.centralManager cancelPeripheralConnection:self.peripheral];
            [self otaCallback:@"DFUStateCompleted"];
            
            NSLog(@"%d",self.dfuController.paused);
            
            break;
        }
        default:
            break;
    }
    
}

//OTA升級(jí)的回調(diào)
- (void)otaCallback:(NSString *)state{
    
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self.delegate respondsToSelector:@selector(onFSMOtaUpgradeResult:)])
        {
            [self.delegate onFSMOtaUpgradeResult:state];
        }
    });
}

參考文章:

劉彥瑋的技術(shù)博客
Nordic
iOS藍(lán)牙開發(fā)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容