iOS連接外設的代碼實現

Ivey1982 8年前發布 | 21K 次閱讀 iOS開發 移動開發

來自: http://www.cocoachina.com/ios/20160217/15294.html

上一篇文章介紹了藍牙的技術知識,這里我們具體說明一下中心模式的應用場景。主設備(手機去掃描連接外設,發現外設服務和屬性,操作服務和屬性的應用。一般來說,外設(藍牙設備,比如智能手環之類的東西), 會由硬件工程師開發好,并定義好設備提供的服務,每個服務對于的特征,每個特征的屬性(只讀,只寫,通知等等),本文例子的業務場景,就是用一手機app去讀寫藍牙設備。

##ios連接外設的代碼實現流程

  1. 建立中心角色

  2. 掃描外設(discover)

  3. 連接外設(connect)

  4. 掃描外設中的服務和特征(discover)

- 4.1 獲取外設的services

- 4.2 獲取外設的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值

5. 與外設做數據交互(explore and interact)

6. 訂閱Characteristic的通知

7.斷開連接(disconnect)

8.模擬器藍牙調試,慎用,最好還是用真機去調試。

##準備環境

  1. Xcode

  2. 開發證書和手機(藍牙程序需要使用使用真機調試,使用模擬器也可以調試,但是方法很蛋疼,我會放在最后說)

  3. 藍牙外設

##實現步驟

###1 導入CoreBluetooth頭文件,建立主設備管理類,設置主設備委托

#import [CoreBluetooth/CoreBluetooth.h](因識別問題,此處將尖括號改為方括號)
    @interface ViewController : UIViewController    @interface ViewController (){
        //系統藍牙設備管理對象,可以把他理解為主設備,通過他,可以去掃描和鏈接外設
        CBCentralManager *manager;
    }

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

###2 掃描外設(discover),掃描外設的方法我們放在centralManager成功打開的委托中,因為只有設備成功打開,才能開始掃描,否則會報錯。

-(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");
                //開始掃描周圍的外設
                /*
                 第一個參數nil就是掃描周圍所有的外設,掃描到外設后會進入
                      - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
                 */
                [manager scanForPeripheralsWithServices:nil options:nil];
                break;
            default:
                break;
        }

    }
    //掃描到設備會進入方法
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{

        NSLog(@"當掃描到設備:%@",peripheral.name);
        //接下來可以連接設備
    }</pre> 

###3 連接外設(connect)

//掃描到設備會進入方法
        -(void)centralManager:(CBCentralManager )central didDiscoverPeripheral:(CBPeripheral )peripheral advertisementData:(NSDictionary )advertisementData RSSI:(NSNumber )RSSI{
            //接下連接我們的測試設備,如果你沒有設備,可以下載一個app叫lightbule的app去模擬一個設備
            //這里自己去設置下連接規則,我設置的是P開頭的設備
                   if ([peripheral.name hasPrefix:@"P"]){
                   /*
                       一個主設備最多能連7個外設,每個外設最多只能給一個主設備連接,連接成功,失敗,斷開會進入各自的委托

                - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設成功的委托
                - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設連接失敗的委托
                - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設的委托
                */
                //連接設備
               [manager 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]);

    }</pre> 

###4 掃描外設中的服務和特征(discover)

設備連接成功后,就可以掃描設備的服務了,同樣是通過委托形式,掃描到結果后會進入委托方法。但是這個委托已經不再是主設備的委托(CBCentralManagerDelegate),而是外設的委托(CBPeripheralDelegate),這個委托包含了主設備與外設交互的許多 回叫方法,包括獲取services,獲取characteristics,獲取characteristics的值,獲取characteristics的Descriptor,和Descriptor的值,寫數據,讀rssi,用通知的方式訂閱數據等等。

4.1獲取外設的services

//連接到Peripherals-成功

    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
    {
        NSLog(@">>>連接到名稱為(%@)的設備-成功",peripheral.name);
        //設置的peripheral委托CBPeripheralDelegate
        //@interface ViewController : UIViewController            [peripheral setDelegate:self];
        //掃描外設Services,成功后會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
        [peripheral discoverServices:nil];

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

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

    }</pre> 

4.2獲取外設的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的值,讀到數據會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
     for (CBCharacteristic *characteristic in service.characteristics){
         {
             [peripheral readValueForCharacteristic:characteristic];
         }
     }

     //搜索Characteristic的Descriptors,讀到數據會進入方法:-(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,具體開發時,會根據外設協議制定的方式去解析數據
    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
    //這個descriptor都是對于characteristic的描述,一般都是字符串,所以這里我們轉換成字符串去解析
    NSLog(@"characteristic uuid:%@  value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}</pre> 

###5 把數據寫到Characteristic中

//寫數據
    -(void)writeCharacteristic:(CBPeripheral )peripheral
                characteristic:(CBCharacteristic )characteristic
                         value:(NSData *)value{

    //打印出 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的權限才可以寫
    if(characteristic.properties & CBCharacteristicPropertyWrite){
        /*
            最好一個type參數可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區別是是否會有反饋
        */
        [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    }else{
        NSLog(@"該字段不可寫!");
    }


}</pre> 

###6 訂閱Characteristic的通知

    //設置通知
    -(void)notifyCharacteristic:(CBPeripheral )peripheral
                characteristic:(CBCharacteristic )characteristic{
        //設置通知,數據通知會進入:didUpdateValueForCharacteristic方法
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];

}

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

     [peripheral setNotifyValue:NO forCharacteristic:characteristic];
}</pre> 

###7 斷開連接(disconnect)

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

###8 模擬器藍牙調試,慎用,最好還是用真機去調試。

由于在iPhone 4s之后的iOS才支持BLE,新一代的這些iOS設備又都不便宜,在做測試的時候,用iOS模擬器進行調試,可以節約一些開發成本。怎么在iOS模擬器上調試BLE,

蘋果最初給出的說明是,支持BLE的mac機子上可以用模擬器進行調試,并給出了一份技術文檔(傳送門),惡心的是,后來蘋果抽風,又把這份文檔移除, 并且把iOS 7.0的模擬器上對BLE的支持也移除掉了(難道是想讓大家多買設備測試?Apple sucks.)后面,網上搜了一下,解決辦法如下:

1. 買一個CSR藍牙4.0 USB適配器(某寶上大概30塊錢),在機子上插入該物(你懂的)

2. 在Terminal下敲入sudo nvram bluetoothHostControllerSwitchBehavior="never" , 重啟Mac。

3. 用XCode 4.6調試代碼,在iOS 6.1的模擬器上跑程序(用XCode 5.0跑iOS 7.0模擬器會拋異常,原因上面詳訴過了,Apple sucks,你懂的)

如何降低模擬器的IOS版本呢?

XCode->Preferences->Downloads里面有很多simulators你可以下載

選擇個6.1的下載好了

代碼下載

我博客中大部分示例代碼都上傳到了github,地址是:https://github.com/coolnameismy/demo, 點擊跳轉代碼下載地址

本文代碼存放目錄是BleDemo

</div>

 本文由用戶 Ivey1982 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!