iOS 藍芽開發 - swift版

孔雨露發表於2020-04-06

@[TOC](iOS 藍芽開發 )

1.藍芽簡介

  • 藍芽模式簡介 藍芽開發分為兩種模式,中心模式(central),和外設模式(peripheral)。一般來講,我們需要在軟體內連線硬體,通過連線硬體給硬體傳送指令以完成一些動作的藍芽開發都是基於中心模式(central)模式的開發,也就是說我們開發的app是中心,我們要連線的硬體是外設。如果需要其他裝置連線手機藍芽,並對手機進行一些操作,那就是基於外設模式(peripheral)的開發。 本次我們主要介紹的就是中心模式的藍芽開發

  • 裝置簡介 中心裝置(CBCentralManager):iOS系統的手機等裝置 外圍裝置(CBPeripheral):手環等第三方裝置

  • 藍芽資料傳輸簡介 將外圍裝置(車輛)的資料傳送給中心裝置(手機)時, 資料是經過兩層包裝的 第一層是 Service(服務) , 可以是一個或多個, 比如車輛資料(服務) 第二層是 Characteristic(特徵) , 他提供了更多關於Service(服務)的資料, 例如車輛資料(服務)中包含了兩個資料, 分別是里程資料和續航資料, 這兩個就是車輛資料(服務)的具體資料(特徵)

  • 具體操作簡介 讀(read) , 寫(write) , 訂閱(notify) 我們的目的是讀取裝置中的資料(read) , 或者給裝置寫入一定的資料(write)。有時候我們還想裝置的資料變化的時候不需要我們手動去讀取這個值,需要裝置自動通知我們它的值變化了,值是多少。把值告訴app,這個時候就需要訂閱這個特徵了(notify)

  • 其他相關概念

  1. 目前都使用的是低功耗藍芽4.0,藍芽外設必需為4.0及以上(2.0需要MFI認證),否則無法開發,藍芽4.0設施由於低耗電,所以也叫做BLE。
  2. CoreBluetooth框架的核心其實是兩個東西,peripheral和central, 能瞭解成外設和中心,就是你的蘋果手機就是中心,外部藍芽稱為外設。
  3. 服務和特徵(service characteristic):簡而言之,外部藍芽中它有若干個服務service(服務你能瞭解為藍芽所擁有的可以力),而每個服務service下擁有若干個特徵characteristic(特徵你能瞭解為解釋這個服務的屬性)。
  4. Descriptor(形容)使用來形容characteristic變數的屬性。例如,一個descriptor能規定一個可讀的形容,或者者一個characteristic變數可接受的範圍,或者者一個characteristic變數特定的單位。
  5. 我們用的藍芽板塊是在淘寶買的, 大概十多元一個, ios大概每次能接受90個位元組, 安卓大概每次能接收20個位元組, 具體數字可可以會浮動, 應該是與藍芽板塊有關。

2. 藍芽連線

自己實踐的兩個藍芽demo:

  1. OC 編寫的:Bluetooth4_configWifi
  2. swifit編寫的:KYLBluetoothDemo_swift

2.1 CoreBluetooth框架

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

在這裡插入圖片描述

  • 這兩組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
};
複製程式碼

2.2 外設、服務、特徵間的關係

在這裡插入圖片描述

在這裡插入圖片描述

2.3 藍芽連線過程

  1. 建立中心裝置(CBCentralManager)
  2. 中心裝置開始掃描(scanForPeripherals)
  3. 掃描到外圍裝置之後, 自動呼叫中心裝置的代理方法(didDiscoverPeripheral)
  4. 如果裝置過多, 可以將掃描到的外圍裝置新增到陣列
  5. 開始連線, 從陣列中過濾出自己想要的裝置, 進行連線(connectPeripheral)
  6. 連線上之後, 自動呼叫中心裝置的代理方法(didConnectPeripheral), 在代理中, 進行查詢外圍裝置的服務(peripheral.discoverServices)
  7. 查詢到服務之後, 自動呼叫外圍裝置的代理(didDiscoverServices), 可通過UUID,查詢具體的服務,查詢服務(discoverCharacteristics)
  8. 查詢到特徵之後, 自動呼叫外圍裝置的代理(didDiscoverCharacteristics), 通過UUID找到自己想要的特徵, 讀取特徵(readValueForCharacteristic)
  9. 讀取到特徵之後, 自動呼叫外設的代理方法(didUpdateValueForCharacteristic),在這裡列印或者解析自己想要的特徵值.

2.4 藍芽中心模式,外設模式

2.4.1 藍芽中心模式

  • 中心模式流程
  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)

2.4.2 藍芽外設模式

  • 藍芽外設模式流程
  1. 啟動一個Peripheral管理物件

  2. 本地Peripheral設定服務,特性,描述,許可權等等

  3. Peripheral傳送廣告

  4. 設定處理訂閱、取消訂閱、讀characteristic、寫characteristic的委託方法

2.5 藍芽裝置狀態

  • 藍芽裝置狀態 待機狀態(standby):裝置沒有傳輸和傳送資料,並且沒有連線到任何設 廣播狀態(Advertiser):週期性廣播狀態 掃描狀態(Scanner):主動尋找正在廣播的裝置 發起連結狀態(Initiator):主動向掃描裝置發起連線。 主裝置(Master):作為主裝置連線到其他裝置。 從裝置(Slave):作為從裝置連線到其他裝置。

  • 藍芽裝置的五種工作狀態 準備(standby) 廣播(advertising) 監聽掃描(Scanning 發起連線(Initiating) 已連線(Connected)

2.6 藍芽連線程式碼實現

  1. 初始化
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
複製程式碼
  1. 搜尋掃描外圍裝置
/**
 *  --  初始化成功自動呼叫
 *  --  必須實現的代理,用來返回建立的centralManager的狀態。
 *  --  注意:必須確認當前是CBCentralManagerStatePoweredOn狀態才可以呼叫掃描外設的方法:
 scanForPeripheralsWithServices
 */
- (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表示預設掃描所有可見藍芽裝置。
             -- 注意:第一個引數是用來掃描有指定服務的外設。然後有些外設的服務是相同的,比如都有FFF5服務,那麼都會發現;而有些外設的服務是不可見的,就會掃描不到裝置。
             -- 成功掃描到外設後呼叫didDiscoverPeripheral
             */
            [self.centralManager scanForPeripheralsWithServices:nil options:nil];
        }
            break;
        default:
            break;
    }
}

#pragma mark 發現外設
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber *)RSSI{
    NSLog(@"Find device:%@", [peripheral name]);
    if (![_deviceDic objectForKey:[peripheral name]]) {
        NSLog(@"Find device:%@", [peripheral name]);
        if (peripheral!=nil) {
            if ([peripheral name]!=nil) {
                if ([[peripheral name] hasPrefix:@"根據裝置名過濾"]) {
                    [_deviceDic setObject:peripheral forKey:[peripheral name]];
                     // 停止掃描, 看需求決定要不要加
//                    [_centralManager stopScan];
                    // 將裝置資訊傳到外面的頁面(VC), 構成掃描到的裝置列表
                    if ([self.delegate respondsToSelector:@selector(dataWithBluetoothDic:)]) {
                        [self.delegate dataWithBluetoothDic:_deviceDic];
                    }
                }
            }
        }
    }
}

複製程式碼
  1. 連線外圍裝置
// 連線裝置(.h中宣告出去的介面, 一般在點選裝置列表連線時呼叫)
- (void)connectDeviceWithPeripheral:(CBPeripheral *)peripheral
{
    [self.centralManager connectPeripheral:peripheral options:nil];
}



#pragma mark 連線外設--成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //連線成功後停止掃描,節省記憶體
    [central stopScan];
    peripheral.delegate = self;
    self.peripheral = peripheral;
    //4.掃描外設的服務
    /**
     --     外設的服務、特徵、描述等方法是CBPeripheralDelegate的內容,所以要先設定代理peripheral.delegate = self
     --     參數列示你關心的服務的UUID,比如我關心的是"FFE0",引數就可以為@[[CBUUID UUIDWithString:@"FFE0"]].那麼didDiscoverServices方法回撥內容就只有這兩個UUID的服務,不會有其他多餘的內容,提高效率。nil表示掃描所有服務
     --     成功發現服務,回撥didDiscoverServices
     */
    [peripheral discoverServices:@[[CBUUID UUIDWithString:@"你要用的服務UUID"]]];
    if ([self.delegate respondsToSelector:@selector(didConnectBle)]) {
       // 已經連線
        [self.delegate didConnectBle];
    }
}



#pragma mark 連線外設——失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"%@", error);
}


#pragma mark 取消與外設的連線回撥
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"%@", peripheral);
}

複製程式碼
  1. 獲得外圍裝置的服務
#pragma mark 發現服務回撥
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
 
    //NSLog(@"didDiscoverServices,Error:%@",error);
    CBService * __nullable findService = nil;
    // 遍歷服務
    for (CBService *service in peripheral.services)
    {
        //NSLog(@"UUID:%@",service.UUID);
        if ([[service UUID] isEqual:[CBUUID UUIDWithString:@"你要用的服務UUID"]])
        {
            findService = service;
        }
    }
    NSLog(@"Find Service:%@",findService);
    if (findService)
        [peripheral discoverCharacteristics:NULL forService:findService];
}


#pragma mark 發現特徵回撥
/**
 --  發現特徵後,可以根據特徵的properties進行:讀readValueForCharacteristic、寫writeValue、訂閱通知setNotifyValue、掃描特徵的描述discoverDescriptorsForCharacteristic。
 **/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    for (CBCharacteristic *characteristic in service.characteristics) {
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"你要用的特徵UUID"]]) {
 
            /**
             -- 讀取成功回撥didUpdateValueForCharacteristic
             */
            self.characteristic = characteristic;
            // 接收一次(是讀一次資訊還是資料經常變實時接收視情況而定, 再決定使用哪個)
//            [peripheral readValueForCharacteristic:characteristic];
            // 訂閱, 實時接收
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
 
            // 傳送下行指令(傳送一條)
            NSData *data = [@"硬體工程師給我的指令, 傳送給藍芽該指令, 藍芽會給我返回一條資料" dataUsingEncoding:NSUTF8StringEncoding];
            // 將指令寫入藍芽
                [self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }
        /**
         -- 當發現characteristic有descriptor,回撥didDiscoverDescriptorsForCharacteristic
         */
        [peripheral discoverDescriptorsForCharacteristic:characteristic];
    }
}


複製程式碼
  1. 從外圍裝置讀取資料
#pragma mark - 獲取值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    // characteristic.value就是藍芽給我們的值(我這裡是json格式字串)
    NSData *jsonData = [characteristic.value dataUsingEncoding:NSUTF8StringEncoding];
        NSDictionary *dataDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
    // 將字典傳出去就可以使用了
}


#pragma mark - 中心讀取外設實時資料
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (characteristic.isNotifying) {
        [peripheral readValueForCharacteristic:characteristic];
    } else { 
        NSLog(@"Notification stopped on %@.  Disconnecting", characteristic);
        NSLog(@"%@", characteristic);
        [self.centralManager cancelPeripheralConnection:peripheral];
    }
}

複製程式碼
  1. 給外圍裝置傳送(寫入)資料

// 上文中發現特徵之後, 傳送下行指令的時候其實就是向藍芽中寫入資料
// 例:
// 傳送檢查藍芽命令
- (void)writeCheckBleWithBle
{
    _style = 1;
    // 傳送下行指令(傳送一條)
    NSData *data = [@"硬體工程師提供給你的指令, 類似於5E16010203...這種很長一串" dataUsingEncoding:NSUTF8StringEncoding];
    [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}



#pragma mark 資料寫入成功回撥
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    NSLog(@"寫入成功");
    if ([self.delegate respondsToSelector:@selector(didWriteSucessWithStyle:)]) {
        [self.delegate didWriteSucessWithStyle:_style];
    }
}

複製程式碼
  1. 停止掃描

#pragma mark 停止掃描外設
- (void)stopScanPeripheral{
    [self.centralManager stopScan];
}


#pragma mark 掃描外設
- (void)scanDevice
{
    if (_centralManager == nil) {
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    [_deviceDic removeAllObjects];
     }
}

複製程式碼
  1. 斷開連線
#pragma mark 斷開連線
- (void)disConnectPeripheral{
    /**
     -- 斷開連線後回撥didDisconnectPeripheral
     -- 注意斷開後如果要重新掃描這個外設,需要重新呼叫[self.centralManager scanForPeripheralsWithServices:nil options:nil];
     */
    [self.centralManager cancelPeripheralConnection:self.peripheral];
}

複製程式碼

相關文章