iOS 藍牙開發和注意點

前言

  • 藍牙傳輸所用的框架是<CoreBluetooth/CoreBluetooth.h>
  • 藍牙鏈接須要中心管理者和外部設備,咱們所作的開發基本是圍繞中心管理來的;
  • 藍牙設備發過來的每一個數據包,爲了保證數據在傳輸的時候沒有丟失,通常須要包頭,包尾,校驗和
  • 有不少藍牙協議很複雜,須要把數據轉化成二進制進行轉化解析,對於高字節,低字節,小端模式,大端模式,符號位,位運算這些基本概念須要瞭解清楚

1.關於Mac地址的獲取

自iOS7以後,蘋果不支持獲取Mac地址,只能用UUID來標識設備,要注意的是同一個設備在不一樣手機上顯示的UUID不相同,但有的設備能夠經過 「180A」這個服務來發現特徵,再來讀取 「2A23」這個特徵值,能夠得到Mac地址。若是你的藍牙設備不支持這樣獲取,你能夠跟硬件工程師溝通,來得到Mac地址,添加一個獲取地址命令或者增長一個含地址的特徵值均可以很容易的獲取。上面獲取地址的前提都是須要先創建鏈接,若是必定要在掃描的時候得到Mac地址,讓硬件工程師把數據寫入廣播包裏,看是否可行;git

2.藍牙鏈接流程

若是你不是新手,又不想浪費時間,請直接看第三點 注意點,核心部分github

  • 創建中心設備管理者
  • 掃描外設
  • 鏈接外設
  • 掃描外設中的服務
  • 掃描外設中的特徵
  • 訂閱或讀取特徵值
  • 獲取外設中的數據

創建中心設備管理者

// 建立以後會立刻檢查藍牙的狀態,nil默認爲主線程
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]

藍牙線程不必去開異步線程,在主線程消耗不了什麼性能數組

掃描外設

// 藍牙狀態發生改變,這個方法必定要實現
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    // 藍牙狀態可用
    if (central.state == CBCentralManagerStatePoweredOn) {

        // 若是藍牙支持後臺模式,必定要指定服務,不然在後臺斷開鏈接不上,若是不支持,可設爲nil, option裏的CBCentralManagerScanOptionAllowDuplicatesKey默認爲NO, 若是設置爲YES,容許搜索到重名,會很耗電
        [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil];
    }
}

鏈接外設

/**
 * 發現設備
 * @param peripheral 設備
 * @param advertisementData 廣播內容
 * @param RSSI 信號強度
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
    // 判斷是不是你須要鏈接的設備
    if ([peripheral.name isEqualToString:kPeripheralName]) {
        peripheral.delegate = self;
        // 必定要記得把外設保存起來
        self.selectedPeripheral = peripheral;
        // 開始鏈接設備
        [self.centralManager connectPeripheral:self.selectedPeripheral options:nil];
    }
}

掃描外設中的服務

/**
 * 已經鏈接上設備
 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    // 中止掃描
    [self.centralManager stopScan];
    // 發現服務
    [self.selectedPeripheral discoverServices:nil];
}

掃描外設中的特徵

/**
 * 已經發現服務
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    for (CBService *service in peripheral.services) {
        if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
            // 根據你要的那個服務去發現特性
            [self.selectedPeripheral discoverCharacteristics:nil forService:service];
        }

        // 這裏我是根據 180A 用來獲取Mac地址,沒什麼實際做用,可刪掉
        if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]) {
            [self.selectedPeripheral discoverCharacteristics:nil forService:service];
        }
    }
}

訂閱或讀取特徵值

/**
 * 已經發現特性
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    for (CBCharacteristic *characteristic in service.characteristics) {
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]) {
            // 這裏是讀取Mac地址, 可不要, 數據固定, 用readValueForCharacteristic, 不用setNotifyValue:setNotifyValue
            [self.selectedPeripheral readValueForCharacteristic:characteristic];
        }


        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
            // 訂閱特性,當數據頻繁改變時,通常用它, 不用readValueForCharacteristic
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];

            // 獲取電池電量
            unsigned char send[4] = {0x5d, 0x08, 0x01, 0x3b};
            NSData *sendData = [NSData dataWithBytes:send length:4];

            // 這裏的type類型有兩種 CBCharacteristicWriteWithResponse CBCharacteristicWriteWithoutResponse,它的屬性枚舉能夠組合
            [self.selectedPeripheral writeValue:sendData forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];

            /*
             characteristic 屬性
             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(@"%@",characteristic);
            // 打印結果爲 <CBCharacteristic: 0x1702a2a00, UUID = FFF6, properties = 0x16, value = (null), notifying = NO>

            //  個人結果 爲 0x16  (0x08 & 0x16)結果不成立, (0x04 & 0x16)結果成立,那寫入類型就是 CBCharacteristicPropertyWriteWithoutResponse
        }
    }
}

獲取外設中的數據

/**
 * 數據更新的回調
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    // 這裏收到的數據都是16進制,有兩種轉換,一種就直接轉字符串,另外一種是轉byte數組,看用哪一種方便

    // 直接轉字符串
    NSString *orStr = characteristic.value.description;
    NSString *str = [orStr substringWithRange:NSMakeRange(1, orStr.length - 2)];
    NSString *dataStr = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"dataStr = %@",dataStr);

    // 轉Byte數組
    Byte *byte = (Byte *)characteristic.value.bytes;

    //_______________________________________________________________________________________________________________
    // 解析你的協議,附幾個解協議或許能用到的函數
}

設備鏈接斷開

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    // 讓它自動重連
    [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil];
}

這是系統代理方法,若是要主動斷開須要調用 - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral; 這個方法框架

寫入數據成功的回調

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
        // 讀取數據
    [self.selectedPeripheral readValueForCharacteristic:characteristic];
}

若是類型是CBCharacteristicWriteWithoutResponse,不會走這個方法;異步

3.注意點,核心部分,請仔細看

  1. 作藍牙前必定要去商城下個LightBlue,一個設備有不少服務,服務中又有不少特性,特性中又分讀的,寫的等,有了LightBlue,你能夠很快的找到你須要的特性;函數


    LightBlue截圖


    從上圖中咱們能夠清晰的看到每一個服務中又多少個特性,特性的屬性Read、Write、Write Without Response、Notify等也標明的很清楚,工具

  2. 通常的藍牙都要支持重連和後臺運行,若是掃描設備的時候,用這個方法- (void)scanForPeripheralsWithServices:options:沒有指定特定的服務,而是用nil代替,設備在後臺斷開的時候是不會重連的;性能

  3. 藍牙是能夠同時鏈接多個外部設備ui

  4. 關於readValueForCharacteristicsetNotifyValue:forCharacteristic: 的區別, readValueForCharacteristic適合用來讀取數據不怎麼更新的特徵值, 若是獲取的數據是常常更新的,那就 必定要用setNotifyValue:forCharacteristic:來訂閱這個特徵;線程

  5. 當咱們寫入命令時writeValue:forCharacteristic:type:,這個type類型到時是用CBCharacteristicWriteWithResponse仍是用CBCharacteristicWriteWithoutResponse會有疑惑,先看一下特性屬性的枚舉,它們是能夠組合的

    /*
          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
          };

    再來看看我打印的兩個特徵值,第一個是獲取Mac地址的特性,另外一個是獲取數據的特性
    <CBCharacteristic: 0x1700b8ae0, UUID = System ID, properties = 0x2, value = (null), notifying = NO>
    <CBCharacteristic: 0x1702a2a00, UUID = FFF6, properties = 0x16, value = (null), notifying = NO>
    第一個0x2對應只可讀, 第二個 (0x16 & 0x08)不成立,(0x16 & 0x04)成立,因此用CBCharacteristicWriteWithoutResponse,並且這個特徵值還可讀,能夠通知

  6. 代理方法- (void)centralManagerDidUpdateState:(CBCentralManager *)central;必定要調用,不然會報錯,這個方法只要設置中心設備的代理以後,就必定會走,咱們最開始的掃描外設應放在這個方法裏;

  7. 對因而否要單首創建一個工具類來獲取藍牙數據,若是隻是一個界面須要用到藍牙數據,我以爲徹底不必,若是是多個界面的話,最好仍是建立一個工具類。

  8. 若是藍牙支持要支持後臺模式,只須要去把藍牙後臺模式打開


    後臺運行藍牙


    記住只要勾選Uses Bluetooth LE accessories就好了,別勾選Acts As a Bluetooth LE accessory,除非你把你的手機當作外部設備使用;

簡單又詳細的Demo地址

 
做者:alenpaulkevin連接:http://www.jianshu.com/p/0ccfd53fc559來源:簡書著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索