iOS藍牙開發 (二)iOS連接外設的代碼實現

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

二、準備環境

  1. xcode
  2. 開發證書和手機
    3.藍牙外設

三、實現步驟
1.導入CoreBluetooth頭文件,建立主設備管理類,設置主設備代理

#import <CoreBluetooth/CoreBluetooth.h>

@interface BeCentralVewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
@interface BeCentralVewController (){
    //系統藍牙設備管理對象,可以把他理解為主設備,通過他,可以去掃描和鏈接外設
    CBCentralManager *manager;
    //用于保存被發現設備
    NSMutableArray *discoverPeripherals;
}

一些代理的信息

/*
     設置主設備的委托,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 
*/
  1. 掃描外設(discover)
    掃描外設的方法我們放在centralManagerDidUpdateState,只有設備成功打開,才能開始掃描,否則會報錯。
-(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;
             */
            [central scanForPeripheralsWithServices:nil options:nil];
            
            break;
        default:
            break;
    }
    
}

掃描到設備后會進入下面方法

  -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{

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

3.連接設備(connect)

-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    
    NSLog(@"當掃描到設備:%@",peripheral.name);
    //接下連接我們的測試設備,如果你沒有設備,可以下載一個app叫lightbule的app去模擬一個設備
    //這里自己去設置下連接規則,我設置的是P開頭的設備
//    if ([peripheral.name hasPrefix:@"P"]){
        /*
         一個主設備最多能連7個外設,每個外設最多只能給一個主設備連接,連接成功,失敗,斷開會進入各自的委托
         - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設成功的委托
         - (void)centra`lManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設連接失敗的委托
         - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設的委托
         */
  
        //找到的設備必須持有它,否則CBCentralManager中也不會保存peripheral,那么CBPeripheralDelegate中的方法也不會被調用!!
        [discoverPeripherals addObject:peripheral];
        [central connectPeripheral:peripheral options:nil];
//    }  
}

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

**
有一個點非常容易出錯,大家請注意,在didDiscoverPeripheral這個代理中有一行,

/找到的設備必須持有它,否則CBCentralManager中也不會保存peripheral,那么CBPeripheralDelegate中的方法也不會被調用!!
        [discoverPeripherals addObject:peripheral];

請特別注意,如果不保存,會影響到后面的方法執行,這個地方最容易出錯,大家也可以看下,在xcode中的說明,重點看@discussion中的內容,里面特別指出了重要的retained對象。

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


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

//掃描到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];
    }
    
}

4.2獲取外設的Chatacteristics,獲取Chatacteristics的值,獲取Chatacteristics的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);
}

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(@"該字段不可寫!");
    }    
}

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];
}

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的下載好了
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 這里我們具體說明一下中心模式的應用場景。主設備(手機去掃描連接外設,發現外設服務和屬性,操作服務和屬性的應用。一般...
    丶逝水流年閱讀 2,279評論 3 4
  • iOS連接外設的代碼實現流程 1. 建立中心角色 2. 掃描外設(discover) 3. 連接外設(connec...
    UILabelkell閱讀 2,438評論 2 4
  • (一) iOS藍牙開發藍牙相關基礎知識 藍牙常見名稱和縮寫 MFI ======= make for ipad ...
    雷鳴1010閱讀 5,017評論 2 12
  • 很多時候我們總以為來日方長,可是一不小心就后會無期了…… 我和青子來自兩個不同的地方,他是河南人,我很喜歡聽他...
    大樹姑娘閱讀 874評論 1 4
  • 紅衰綠減小院幽,落盡繁華青紅褪。 ——讀友的詩《煙花故里路,何年與君逢》有感 紅與綠終會被荒涼和干枯所代替, 春,...
    Angel李子汐閱讀 856評論 8 32