1. 程式人生 > >iOS藍芽4.0協議簡單介紹

iOS藍芽4.0協議簡單介紹

iOS開發藍芽4.0的框架是CoreBluetooth,本文主要介紹CoreBluetooth的使用,關於本文中的程式碼片段大多來自github上的一個demo,地址是myz1104/Bluetooth

在CoreBluetooth中有兩個主要的部分,Central和Peripheral,有一點類似Client Server。CBPeripheralManager 作為周邊裝置是伺服器。CBCentralManager作為中心裝置是客戶端。所有可用的iOS裝置可以作為周邊(Peripheral)也可以作為中央(Central),但不可以同時既是周邊也是中央。

一般手機是客戶端, 裝置(比如手環)是伺服器,因為是手機去連線手環這個伺服器。周邊(Peripheral)是生成或者儲存了資料的裝置,中央(Central)是使用這些資料的裝置。你可以認為周邊是一個廣播資料的裝置,他廣播到外部世界說他這兒有資料,並且也說明了能提供的服務。另一邊,中央開始掃描附近有沒有服務,如果中央發現了想要的服務,然後中央就會請求連線周邊,一旦連線建立成功,兩個裝置之間就開始交換傳輸資料了。

除了中央和周邊,我們還要考慮他倆交換的資料結構。這些資料在服務中被結構化,每個服務由不同的特徵(Characteristics)組成,特徵是包含一個單一邏輯值的屬性型別。

Peripheral的實現步驟

首先是建立一個周邊

_peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];

接下來它就會響應代理的peripheralManagerDidUpdateState方法,可以獲得peripheral的狀態等資訊,

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
    switch (peripheral.state)
    {
        case CBPeripheralManagerStatePoweredOn:
        {
            [self setupService];
        }
            break;

        default:
        {
            NSLog(@"Peripheral Manager did change state");
        }
            break;
    }
}

當發現周邊裝置的藍芽是可以的時候,這就需要去準備你需要廣播給其他中央裝置的服務和特徵了,這裡通過呼叫setupService方法來實現。 每一個服務和特徵都需要用一個UUID(unique identifier)去標識,UUID是一個16bit或者128bit的值。如果你要建立你的中央-周邊App,你需要建立你自己的128bit的UUID。你必須要確定你自己的UUID不能和其他已經存在的服務衝突。如果你正要建立一個自己的裝置,需要實現標準委員會需求的UUID;如果你只是建立一箇中央-周邊App,我建議你開啟Mac OS X的Terminal.app,用uuidgen命令生成一個128bit的UUID。你應該用該命令兩次,生成兩個UUID,一個是給服務用的,一個是給特徵用的。然後,你需要新增他們到中央和周邊App中。現在,在view controller的實現之前,我們新增以下的程式碼:

static NSString * const kServiceUUID = @"1C85D7B7-17FA-4362-82CF-85DD0B76A9A5";
static NSString * const kCharacteristicUUID = @"7E887E40-95DE-40D6-9AA0-36EDE2BAE253";

下面就是setupService方法

- (void)setupService
{
    CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];

    self.customCharacteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];

    self.customService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
    [self.customService setCharacteristics:@[self.customCharacteristic]];
    [self.peripheralManager addService:self.customService];


}

當呼叫了CBPeripheralManager的addService方法後,這裡就會響應CBPeripheralManagerDelegate的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error方法。這個時候就可以開始廣播我們剛剛建立的服務了。

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
{
    if (error == nil)
    {
        [self.peripheralManager startAdvertising:@{ CBAdvertisementDataLocalNameKey : @"ICServer", CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:kServiceUUID]] }];
    }
}

當然到這裡,你已經做完了peripheralManager的工作了,中央裝置已經可以接受到你的服務了。不過這是靜止的資料,你還可以呼叫- (BOOL)updateValue:(NSData *)value forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:(NSArray *)centrals方法可以給中央生成動態資料的地方。

- (void)sendToSubscribers:(NSData *)data {
  if (self.peripheral.state != CBPeripheralManagerStatePoweredOn) {
    LXCBLog(@"sendToSubscribers: peripheral not ready for sending state: %d", self.peripheral.state);
    return;
  }

  BOOL success = [self.peripheral updateValue:data
                            forCharacteristic:self.characteristic
                         onSubscribedCentrals:nil];
  if (!success) {
    LXCBLog(@"Failed to send data, buffering data for retry once ready.");
    self.pendingData = data;
    return;
  }
}

central訂閱了characteristic的值,當更新值的時候peripheral會呼叫updateValue: forCharacteristic: onSubscribedCentrals:(NSArray*)centrals去為數組裡面的centrals更新對應characteristic的值,在更新過後peripheral為每一個central走一遍下面的代理方法

- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic

peripheral接受到一個讀或者寫的請求時,會響應以下兩個代理方法

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests

那麼現在peripheral就已經建立好了。

建立一箇中央

建立中央並且連線周邊 現在,我們已經有了一個周邊,讓我們建立我們的中央。中央就是那個處理周邊傳送來的資料的裝置。

self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];

當Central Manager被初始化,我們要檢查它的狀態,以檢查執行這個App的裝置是不是支援BLE。實現CBCentralManagerDelegate的代理方法:

- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    switch (central.state)
    {
        case CBCentralManagerStatePoweredOn:
        {
            [self.manager scanForPeripheralsWithServices:@[ [CBUUID UUIDWithString:kServiceUUID]]
                                                 options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
        }
            break;
        default:
        {
            NSLog(@"Central Manager did change state");
        }
            break;
    }
}

當app的裝置是支援藍芽的時候,需要呼叫CBCentralManager例項的- (void)scanForPeripheralsWithServices:(NSArray *)serviceUUIDs options:(NSDictionary *)options方法,用來尋找一個指定的服務的peripheral。一旦一個周邊在尋找的時候被發現,中央的代理會收到以下回調:

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

    NSString *UUID = [peripheral.identifier UUIDString];
    NSString *UUID1 = CFBridgingRelease(CFUUIDCreateString(NULL, peripheral.UUID));
    NSLog(@"----發現外設----%@%@", UUID,UUID1);
    [self.manager stopScan];

    if (self.peripheral != peripheral)
    {
        self.peripheral = peripheral;
        NSLog(@"Connecting to peripheral %@", peripheral);
        [self.manager connectPeripheral:peripheral options:nil];
    }
}

這個時候一個附帶著廣播資料和訊號質量(RSSI-Received Signal Strength Indicator)的周邊被發現。這是一個很酷的引數,知道了訊號質量,你可以用它去判斷遠近。任何廣播、掃描的響應資料儲存在advertisementData 中,可以通過CBAdvertisementData 來訪問它。 這個時候你用可以連線這個周邊裝置了,

[self.manager connectPeripheral:peripheral options:nil];

它會響應下面的代理方法,

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"----成功連線外設----");
    [self.peripheral setDelegate:self];
    [self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID]]];
}

訪問周邊的服務 上面的CBCentralManagerDelegate代理會返回CBPeripheral例項,它的- (void)discoverServices:(NSArray *)serviceUUIDs方法就是訪問周邊的服務了,這個方法會響應CBPeripheralDelegate的方法。

- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error
{
    NSLog(@"----didDiscoverServices----Error:%@",error);
    if (error)
    {
        NSLog(@"Error discovering service: %@", [error localizedDescription]);
        [self cleanup];
        return;
    }

    for (CBService *service in aPeripheral.services)
    {
        NSLog(@"Service found with UUID: %@", service.UUID);
        if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]])
        {
            [self.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kCharacteristicUUID],[CBUUID UUIDWithString:kWrriteCharacteristicUUID]] forService:service];
        }
    }
}

在上面的方法中如果沒有error,可以呼叫discoverCharacteristics方法請求周邊去尋找它的服務所列出的特徵,它會響應下面的方法

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    if (error)
    {
        NSLog(@"Error discovering characteristic: %@", [error localizedDescription]);
        return;
    }
    if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]])
    {
        for (CBCharacteristic *characteristic in service.characteristics)
        {
            NSLog(@"----didDiscoverCharacteristicsForService---%@",characteristic);
            if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]])
            {
                [peripheral readValueForCharacteristic:characteristic];
                [peripheral setNotifyValue:YES forCharacteristic:characteristic];
            }

            if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kWrriteCharacteristicUUID]])
            {
                writeCharacteristic = characteristic;
            }

        }
    }
}

這個時候peripheral可以呼叫兩個方法, [peripheral readValueForCharacteristic:characteristic]這個是讀特徵值的,會響應- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

[peripheral setNotifyValue:YES forCharacteristic:characteristic];會響應- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error)
    {
        NSLog(@"Error changing notification state: %@", error.localizedDescription);
    }

    // Exits if it's not the transfer characteristic
    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]] )
    {
        // Notification has started
        if (characteristic.isNotifying)
        {
            NSLog(@"Notification began on %@", characteristic);
            [peripheral readValueForCharacteristic:characteristic];
        }
        else
        { // Notification has stopped
            // so disconnect from the peripheral
            NSLog(@"Notification stopped on %@.  Disconnecting", characteristic);
            [self.manager cancelPeripheralConnection:self.peripheral];
        }
    }
}

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"----Value---%@",characteristic.value);
    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]])
    {

        if (writeCharacteristic)
        {
            Byte ACkValue[3] = {0};
            ACkValue[0] = 0xe0; ACkValue[1] = 0x00; ACkValue[2] = ACkValue[0] + ACkValue[1];
            NSData *data = [NSData dataWithBytes:&ACkValue length:sizeof(ACkValue)];
            [self.peripheral writeValue:data
                      forCharacteristic:writeCharacteristic
                                   type:CBCharacteristicWriteWithoutResponse];
        }
    }
}

在上面的方法中,- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type是一個對周邊裝置寫資料的方法,它會響應下面的方法

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"---didWriteValueForCharacteristic-----");
    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kWrriteCharacteristicUUID]])
    {
        NSLog(@"----value更新----");

    }
}

這樣,中央裝置也實現了讀寫資料的功能了。