iOS - 藍牙開發(中心模式)


1.CoreBluetooth.

  iOS藍牙的相關操作由CoreBluetooth.framework進行管理。核心主要是兩種場景:peripheral和central, 可以理解成外設和中心。

  在此主要用到了藍牙作為中心設備通訊連接硬件的服務。中心模式流程為:

  1.建立中心角色;

  2.掃描外設 (discover);15:25:21

  3.鏈接外設 (connect);

  4.掃描外設種的服務和特征

    4.1 獲取外設的服務 services;

    4.2 獲取外設的特征 characteristics;獲取特征的descriptor和descriptor的值;

  5.與外設做數據交互,讀寫數據;

  6.訂閱 characteristics 的通知;

  7.斷開。

  其中:peripheral,central == 外設和中心,發起連接的時central,被連接的設備為perilheral;

 service and characteristic === 服務和特征 每個設備會提供服務和特征,類似於服務端的api,但是機構不同。每個外設會有很多服務,每個服務中包含很多字段,這些字段的權限一般分為 讀read,寫write,通知notiy幾種,就是我們連接設備后具體需要操作的內容。

2.代碼實現

在實際開發過程中,可以根據需求將藍牙操作寫成單例模式,便於整體規划開發。

首先,硬件說明文檔中 一般都會給你 三個UUID類似下面這種:

// 設備service UUID
#define BLUETOOTH_SERVICE_UUID                         @"6E400001-B5A3-F393-E0xxx"
// service 下 用來 寫數據的 characteristic 的UUID
#define BLUETOOTH_CHARACTERISTIC_WRITE_UUID         @"6E400002-B5A3-F393-E0xxx" 
// service 下 用來 讀數據的 characteristic 的UUID ,其實這個讀數據一般是在寫完之后,拿到寫入成功硬件的返回通知,所以其實是通知的uuid
#define BLUETOOTH_CHARACTERISTIC_READ_UUID         @"6E400003-B5A3-F393-E0xxx"

//// characteristic 有properite 屬性,其中有對應的 讀,寫,通知 等權限,不同的權限對應不同的功能。

1.建立中心角色

#import <CoreBluetooth/CoreBluetooth.h>

@interface BlueToothService (<CBPeripheralDelegate,CBCentralManagerDelegate>

//系統藍牙設備管理對象,可以把他理解為主設備,通過他,可以去掃描和鏈接外設
@property (nonatomic, strong) CBCentralManager *centerManager;
//保存掃描到的設備
@property (nonatomic, strong) NSMutableArray *peripheralArray;
//當前鏈接的設備
@property (nonatomic, strong) CBPeripheral *curPeripheral;

 - (void)viewDidLoad {
        [super viewDidLoad];
 //初始化並設置委托和線程隊列,最好一個線程的參數可以為nil,默認會就main線程
         manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
}

 

2.掃描外設

 只有當設備藍牙打開狀態下才會掃描外設,開啟掃描之前,需要先判斷藍牙狀態,這個在centeralManager成功打開的委托中。

/**
 *  判斷狀態 - 開始掃描
 */
- (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");
                    //開始掃描周圍的外設
                   [_centerManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @NO}];

                    break;
                default:
                    break;
            }
   
}            

 

   其中  1.  CBCentralManagerScanOptionAllowDuplicatesKey值為 No,表示不重復掃描已發現的設備;

       2.實際掃描時,可能只需要掃描指定設備,第一個nil的參數,可以指定 CBUUID 對象的數組,即services。這時掃描只會返回正在廣告這些服務的設備。

掃描到的結果在下面方法中顯示:

/**
 *  掃描結果
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    NSLog(@"當掃描到設備:%@",peripheral.name);

}

 1.peripheral:是掃描到的設備,保存在數組中,以備后續鏈接。

   2.advertisementData: 設備的廣播信息。一般硬件廠商會在廣播信息中廣播一些設備信息,如:mac地址等。可以通過硬件文檔解相關數據。

 3.RSSI: 設備信號強度。這個值是波動的,硬件方應該也會給一個相應的計算方法,確定信號強度的具體程度值。

特別:在解析 advertisementData 廣播信息的時候,我遇到了一些問題:

        NSData* advData = [dict objectForKey:@"kCBAdvDataManufacturerData"]; 解析出廣播信息的data格式,但是這個格式是 NSInlineData,並不是NSData格式的。在各種百度問大佬之后,終於找到了解析 的方法:具體如下:

#pragma mark 將傳入的NSData類型轉換成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++) {
        //02x 表示兩個位置 顯示的16進制
        [hexString appendString:[NSString stringWithFormat:@"%02lx",(unsigned long)dataBuffer[i]]];
    }
    result = [NSString stringWithString:hexString];
    
    return result;
    
}

3.鏈接外設

 //連接方法,掃描到的peripheral 需要保存下來,且必須持有它
 [_centerManager connectPeripheral:peripheral options:nil];
//連接到Peripherals-成功
        - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
        {
            NSLog(@">>>連接到名稱為(%@)的設備-成功",peripheral.name);
        }

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

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

        }

   4.1掃描外設的服務

//發現服務
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    NSLog(@" service == %@ ---", peripheral.services);
    
    for (CBService *service in peripheral.services) {
        if ([service.UUID isEqual:[CBUUID UUIDWithString:BLUETOOTH_SERVICE_UUID]]) {
            //查找特征
            [self.curPeripheral discoverCharacteristics:nil forService:service];
        }
    }
}

 

 4.2.掃描外設的特征

//發現特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    NSLog(@"chari == %@ ---", service.characteristics);
    
    //write
    for (CBCharacteristic *character in service.characteristics) {
        //寫 CBCharacteristic
        if ([character.UUID isEqual:[CBUUID UUIDWithString:BLUETOOTH_CHARACTERISTIC_WRITE_UUID]]) {
            self.writeCharacteristic = character;
        }
        
        // 通知
        if ([character.UUID isEqual:[CBUUID UUIDWithString:BLUETOOTH_CHARACTERISTIC_READ_UUID]]) {
            self.notifyCharacteristic = character;
            [self.curPeripheral setNotifyValue:YES forCharacteristic:character];
        }
    }
    if (_connectedSuccessBlock) {
        _connectedSuccessBlock(peripheral,self.writeCharacteristic,self.notifyCharacteristic);
    }
}

 

  掃描到特征后,可以根據 上文中 提到的 硬件方 給的對應的uuid 保存獲取到相應的 CBCharacteristic;通知屬性的CBCharacteristic,需要注冊通知,才能拿到返回數據。

5.與外設做數據交互

 寫數據

//寫入數據方法
[self.curPeripheral writeValue:data forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];

//寫成功 返回方法:這個方法 只是表示 此時寫入數據成功,但並不是 硬件在寫入相應明令之后作出的正確回應,正確返回 是在 通知Characteristic 中返回的
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    NSLog(@"char == %@ -- ", characteristic);
}

 

 //打印出 characteristic 的權限,可以看到有很多種,這是一個NS_OPTIONS,就是可以同時用於好幾個值,常見的有read,write,notify,indicate,知知道這幾個基本就夠用了,前連個是讀寫權限,后兩個都是通知,兩種不同的通知方式。
        /*
         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的權限才可以寫

 

其實,如果硬件並沒有給出相應的uuid,也是可以 根據characteristic 的屬性,判斷其是 寫 還是通知的 特征。

 6.獲取通知

  一般情況下,成功寫入數據之后,比方藍牙開鎖命令,成功輸入命令之后,鎖會打開,同時,會在 監聽的通知屬性下characteristic 中,返回 開鎖成功等信息。注冊通知就是上文 掃描characteristic方法。如下:

[self.curPeripheral setNotifyValue:YES forCharacteristic:character];

 

  監聽通知數據返回方法:

//數據更新回掉
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    NSLog(@"char == %@ -- ", characteristic);
    
    NSData *value = characteristic.value;
}

 

拿到更新后的value 數據后,根據 硬件文檔,解析數據,便可得到正確的返回數據。

 7.斷開鏈接

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

 

 

3.我遇到的問題    

 1.首先我做的是藍牙鎖硬件,總體下來感覺第一最重要的:讓硬件方給一個盡可能詳細的文檔!因為這其中不僅包括數據的解析格式,命令格式,uuid等這些,還有可能會有 加密解密算法,具體的初始密鑰或者明文秘文規定等。這些都是很重要的。

2.前面提到的在獲取廣播數據之后 NSInlineData 數據的解析,其實就是將inlinedata轉成 char* 字符格式,接着再轉換成oc的字符串。

3.byte 數組。藍牙命令格式 一般是byte數組,我對這些其實並沒有怎么接觸過。因為我們是要對命令數據 進行加密 之后再寫入。在研究了很久之后,才慢慢理清了具體的操作過程。

 4,一開始在用babybluetooth,確實是走通了,但是總覺得代碼太多,因為需求比較簡單。如果需求比較復雜,或者可以直接使用babybluetooth,很全面的第三方藍牙庫。

還有,一開始有兩個問題沒有清楚導致進度特別慢:沒有寫數據要用 characteristic 的概念,還有不確定寫數據 和 寫入成功 后的數據返回在哪。在理清楚寫 characteristic,和 通知 characteristic的關系后,整個就變得很容易了。

主要是想對使用過的方法和走過的坑進行一下總結,有意見或建議可以聯系我QQ804729713。

 

 

 

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM