iOS藍牙Bluetooth

(一) iOS藍牙開發藍牙相關基礎知識

藍牙常見名稱和縮寫

MFI ======= make for ipad ,iphone, itouch 專們為蘋果設備制作的設備
BLE ==== buletouch low energy,藍牙4.0設備因為低耗電,所以也叫做BLE
peripheral,central == 外設和中心,發起連接的時central,被連接的設備為perilheral
service and characteristic === 服務和特征 每個設備會提供服務和特征,類似于服務端的api,但是機構不同。每個外設會有很多服務,每個服務中包含很多字段,這些字段的權限一般分為 讀read,寫write,通知notiy幾種,就是我們連接設備后具體需要操作的內容。
Description 每個characteristic可以對應一個或多個Description用戶描述characteristic的信息或屬性
MFI === 開發使用ExternalAccessory 框架
4.0 BLE === 開發使用CoreBluetooth 框架

藍牙基礎知識

CoreBluetooth框架的核心其實是兩個東西,peripheral和central, 可以理解成外設和中心。對應他們分別有一組相關的API和類

CoreBluetoothFramework.jpeg

這兩組api分別對應不同的業務場景,左側叫做中心模式,就是以你的app作為中心,連接其他的外設的場景,而右側稱為外設模式,使用手機作為外設別其他中心設備操作的場景。
服務和特征,特征的屬性(service and characteristic):
每個設備都會有一些服務,每個服務里面都會有一些特征,特征就是具體鍵值對,提供數據的地方。每個特征屬性分為這么幾種:讀,寫,通知這么幾種方式。

  //objcetive c特征的定義枚舉
  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
  };

外設、服務、特征間的關系

TreeOfServicesAndCharacteristics_Remote_2x.png

藍牙中心模式流程

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

藍牙外設模式流程

  1. 啟動一個Peripheral管理對象
  1. 本地Peripheral設置服務,特性,描述,權限等等
  2. Peripheral發送廣告
  3. 設置處理訂閱、取消訂閱、讀characteristic、寫characteristic的委托方法

藍牙設備狀態

  1. 待機狀態(standby):設備沒有傳輸和發送數據,并且沒有連接到任何設
  1. 廣播狀態(Advertiser):周期性廣播狀態
  2. 掃描狀態(Scanner):主動尋找正在廣播的設備
  3. 發起鏈接狀態(Initiator):主動向掃描設備發起連接。
  4. 主設備(Master):作為主設備連接到其他設備。
  5. 從設備(Slave):作為從設備連接到其他設備。

藍牙設備的五種工作狀態

準備(standby)
廣播(advertising)
監聽掃描(Scanning
發起連接(Initiating)
已連接(Connected)

藍牙和版本的使用限制

藍牙2.0 === 越獄設備
藍牙4.0 === IOS6 以上
MFI認證設備(Make For ipod/ipad/iphone) === 無限制

名詞解釋

GAAT :
Generic Attribute Profile , GATT配置文件是一個通用規范,用于在BLE鏈路上發送和接收被稱為“屬性”的數據塊。目前所有的BLE應用都基于GATT。 藍牙SIG規定了許多低功耗設備的配置文件。配置文件是設備如何在特定的應用程序中工作的規格說明。注意一個設備可以實現多個配置文件。例如,一個設備可能包括心率監測儀和電量檢測。
Characteristic:
一個characteristic包括一個單一變量和0-n個用來描述characteristic變量的descriptor,characteristic可以被認為是一個類型,類 似于類。
Descriptor:
Descriptor用來描述characteristic變量的屬性。例如,一個descriptor可以規定一個可讀的描述,或者一個characteristic變量可接受的范圍,或者一個characteristic變量特定的測量單位。 Service service是characteristic的集合。例如,你可能有一個叫“Heart Rate Monitor(心率監測儀)”的service,它包括了很多characteristics,如“heart rate measurement(心率測量)”等。你可以在bluetooth.org 找到一個目前支持的基于GATT的配置文件和服務列表。

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

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

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

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

準備環境

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

實現步驟

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

#import <CoreBluetooth/CoreBluetooth.h>
    @interface ViewController : UIViewController<CBCentralManagerDelegate>


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

    - (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()];

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);
            //接下來可以連接設備

        }

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;//斷開外設的委托
                    */

                    //找到的設備必須持有它,否則CBCentralManager中也不會保存peripheral,那么CBPeripheralDelegate中的方法也不會被調用?。?                    [peripherals addObject:peripheral];
                    //連接設備
                   [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]);

        }

有一點非常容易出錯,大家請注意。在 didDiscoverPeripheral這個委托中有這一行

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

請特別注意,如果不保存,會影響到后面的方法執行,這個地方很多人出錯,在我的藍牙交流群中每天幾乎都會因為這個問題導致無法連接和對外設后續的操作。

大家也可以看一下這個委托在xcode中的說明,重點看@discussion中的內容,里面特別指出了需要retained對象。

/*!
 *  @method centralManager:didDiscoverPeripheral:advertisementData:RSSI:
 *
 *  @param central              The central manager providing this update.
 *  @param peripheral           A <code>CBPeripheral</code> object.
 *  @param advertisementData    A dictionary containing any advertisement and scan response data.
 *  @param RSSI                 The current RSSI of <i>peripheral</i>, in dBm. A value of <code>127</code> is reserved and indicates the RSSI
 *                              was not available.
 *
 *  @discussion                 This method is invoked while scanning, upon the discovery of <i>peripheral</i> by <i>central</i>. A discovered peripheral must
 *                              be retained in order to use it; otherwise, it is assumed to not be of interest and will be cleaned up by the central manager. For
 *                              a list of <i>advertisementData</i> keys, see {@link CBAdvertisementDataLocalNameKey} and other similar constants.
 *
 *  @seealso                    CBAdvertisementData.h
 *
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;

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<CBCentralManagerDelegate,CBPeripheralDelegate>
            [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];
                         }

        }

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

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的下載好了

demo地址是:https://github.com/coolnameismy/demo
點擊跳轉代碼下載地址,本文代碼存放目錄是BleDemo

三:ios藍牙開發app作為外設被連接的實現

再上一節說了app作為central連接peripheral的情況,這一節介紹如何使用app發布一個peripheral,給其他的central連接

peripheral模式的流程

  1. 打開peripheralManager,設置peripheralManager的委托
  1. 創建characteristics,characteristics的description 創建service,把characteristics添加到service中,再把service添加到peripheralManager中
  2. 開啟廣播advertising
  3. 對central的操作進行響應
    • 4.1 讀characteristics請求
    • 4.2 寫characteristics請求
    • 4.4 訂閱和取消訂閱characteristics

準備環境

1 xcode
2 開發證書和手機(藍牙程序需要使用使用真機調試,使用模擬器也可以調試,但是方法很蛋疼,我會放在最后說),如果不行可以使用osx程序調試
3 藍牙外設

實現步驟

  1. 打開peripheralManager,設置peripheralManager的委托

設置當前ViewController實現CBPeripheralManagerDelegate委托

 @interface BePeripheralViewController : UIViewController<CBPeripheralManagerDelegate>

初始化peripheralManager

 /*
     和CBCentralManager類似,藍牙設備打開需要一定時間,打開成功后會進入委托方法
     - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
     模擬器永遠也不會得CBPeripheralManagerStatePoweredOn狀態
     */
    peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];

2. 創建characteristics,characteristics的description ,創建service,把characteristics添加到service中,再把service添加到peripheralManager中

在委托方法 - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral中,當peripheral成功打開后,才可以配置service和characteristics。 這里創建的service和chara對象是CBMutableCharacteristic和CBMutableService。他們的區別就像NSArray和NSMutableArray區別類似。 我們先創建characteristics和description,description是characteristics的描述,描述分很多種, 這里不細說了,常用的就是CBUUIDCharacteristicUserDescriptionString。

//peripheralManager狀態改變
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
    switch (peripheral.state) {
            //在這里判斷藍牙設別的狀態  當開啟了則可調用  setUp方法(自定義)
        case CBPeripheralManagerStatePoweredOn:
            NSLog(@"powered on");
            [info setText:[NSString stringWithFormat:@"設備名%@已經打開,可以使用center進行連接",LocalNameKey]];
            [self setUp];
            break;
        case CBPeripheralManagerStatePoweredOff:
            NSLog(@"powered off");
            [info setText:@"powered off"];
            break;

        default:
            break;
    }
}

//配置bluetooch的
 -(void)setUp{

        //characteristics字段描述
        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];
        //設置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初始化并加入兩個characteristics
        CBMutableService *service1 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];
        [service1 setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];

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

        //添加后就會調用代理的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
        [peripheralManager addService:service1];
        [peripheralManager addService:service2];
 }

3. 開啟廣播advertising

//perihpheral添加了service
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
    if (error == nil) {
        serviceNum++;
    }

    //因為我們添加了2個服務,所以想兩次都添加完成后才去發送廣播
    if (serviceNum==2) {
        //添加服務后可以在此向外界發出通告 調用完這個方法后會調用代理的
        //(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
        [peripheralManager startAdvertising:@{
                                              CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:ServiceUUID1],[CBUUID UUIDWithString:ServiceUUID2]],
                                              CBAdvertisementDataLocalNameKey : LocalNameKey
                                             }
         ];

    }

}

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

4. 對central的操作進行響應

  • 4.1 讀characteristics請求
  • 4.2 寫characteristics請求
  • 4.3 訂閱和取消訂閱characteristics
//訂閱characteristics
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
    NSLog(@"訂閱了 %@的數據",characteristic.UUID);
    //每秒執行一次給主設備發送一個當前時間的秒數
    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(@"取消訂閱 %@的數據",characteristic.UUID);
    //取消回應
    [timer invalidate];
}

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

    //執行回應Central通知數據
    return  [peripheralManager updateValue:[[dft stringFromDate:[NSDate date]] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:nil];

}


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


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

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


}

地址是:https://github.com/coolnameismy/demo,
點擊跳轉代碼下載地址
本文代碼存放目錄是BleDemo

(四)ios藍牙開發BabyBluetooth藍牙庫介紹

BabyBluetooth 是一個最簡單易用的藍牙庫,基于CoreBluetooth的封裝,并兼容ios和mac OS X。

特色:

基于原生CoreBluetooth框架封裝的輕量級的開源庫,可以幫你更簡單地使用CoreBluetooth API。
CoreBluetooth所有方法都是通過委托完成,代碼冗余且順序凌亂。BabyBluetooth使用block方法,可以重新按照功能和順序組織代碼,并提供許多方法減少藍牙開發過程中的代碼量。
鏈式方法體,代碼更簡潔、優雅。
通過channel切換區分委托調用,并方便切換

來源

最近幾個月都在做藍牙項目,用CoreBluetooch感覺語句寫的到處都是,不優雅。一整條鏈下來要近10幾個委托方法,并且不斷的在委托方法中調用方法再進入其他的委托,導致 代碼很零散。因此我就想讓coreBluetooth使用更簡單,語法更優雅,所以開始寫這個BabyBluetooch藍牙庫。

Quick Example

//導入.h文件和系統藍牙庫的頭文件
#import "BabyBluetooth.h"

-(void)viewDidLoad {
    [super viewDidLoad];

   //初始化BabyBluetooth 藍牙庫
    baby = [BabyBluetooth shareBabyBluetooth];
     //設置藍牙委托
     [self babyDelegate];
     //設置委托后直接可以使用,無需等待CBCentralManagerStatePoweredOn狀態
     baby.scanForPeripherals().begin()
}

//藍牙網關初始化和委托方法設置
-(void)babyDelegate{

    //設置掃描到設備的委托
    [baby setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
        NSLog(@"搜索到了設備:%@",peripheral.name);
    }];
    //設置設備連接成功的委托
    [baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) {
        NSLog(@"設備:%@--連接成功",peripheral.name);
    }];
    //設置發現設備的Services的委托
    [baby setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error) {
        for (CBService *service in peripheral.services) {
            NSLog(@"搜索到服務:%@",service.UUID.UUIDString);
        }
    }];
    //設置發現設service的Characteristics的委托
    [baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) {
        NSLog(@"===service name:%@",service.UUID);
        for (CBCharacteristic *c in service.characteristics) {
            NSLog(@"charateristic name is :%@",c.UUID);
        }
    }];
    //設置讀取characteristics的委托
    [baby setBlockOnReadValueForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristics, NSError *error) {
        NSLog(@"characteristic name:%@ value is:%@",characteristics.UUID,characteristics.value);
    }];
    //設置發現characteristics的descriptors的委托
    [baby setBlockOnDiscoverDescriptorsForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristic, NSError *error) {
        NSLog(@"===characteristic name:%@",characteristic.service.UUID);
        for (CBDescriptor *d in characteristic.descriptors) {
            NSLog(@"CBDescriptor name is :%@",d.UUID);
        }
    }];
    //設置讀取Descriptor的委托
    [baby setBlockOnReadValueForDescriptors:^(CBPeripheral *peripheral, CBDescriptor *descriptor, NSError *error) {
        NSLog(@"Descriptor name:%@ value is:%@",descriptor.characteristic.UUID, descriptor.value);
    }];

    //過濾器
    //設置查找設備的過濾器
    [baby setDiscoverPeripheralsFilter:^BOOL(NSString *peripheralsFilter) {
        //設置查找規則是名稱大于1 , the search rule is peripheral.name length > 1
        if (peripheralsFilter.length >1) {
            return YES;
        }
        return NO;
    }];

    //設置連接的設備的過濾器
     __block BOOL isFirst = YES;
    [baby setFilterOnConnetToPeripherals:^BOOL(NSString *peripheralName) {
        //這里的規則是:連接第一個AAA打頭的設備
        if(isFirst && [peripheralName hasPrefix:@"AAA"]){
            isFirst = NO;
            return YES;
        }
        return NO;
    }];


}

CoreBluetooch中實現上掃描,連接,發現服務和characteristic以及它的值相關方法調用是很麻煩啰嗦凌亂的。如下: centralManager啟動->狀態委托->調用掃描方法->進入掃描到設備的委托->調用連接設備方法->進入連接到設備的委托->發現服務方法->發現服務委托-> 發現characteristic方法->發現characteristic委托->讀characteristic的value->讀characteristic的value的委托->讀description,讀description的value-> ….的委托

而BabyBluetooth只需要一句話就執行了上面的內容。

//掃描設備 然后讀取服務,然后讀取characteristics名稱和值和屬性,獲取characteristics對應的description的名稱和值
  baby.scanForPeripherals().connectToPeripheral().discoverServices()
  .discoverCharacteristics().readValueForCharacteristic().discoverDescriptorsForCharacteristic()
  .readValueForDescriptors().begin();

另一方面,BabyBluetooth所有的委托方法都緊湊的聚在了一起。此外,快速示例中沒有包括channel的使用,如果包括了channel,那么ios幾個頁面或者組件的藍牙 調用模塊都可以寫在一起,看起來就覺得很方便。

轉發自:http://liuyanwei.jumppo.com/2015/08/14/ios-BLE-2.html

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

推薦閱讀更多精彩內容