iOS 藍牙開發(fā) - swift版

@[TOC](iOS 藍牙開發(fā) )

1.藍牙簡介

  • 藍牙模式簡介
    藍牙開發(fā)分為兩種模式,中心模式(central),和外設模式(peripheral)。一般來講涂召,我們需要在軟件內(nèi)連接硬件麻捻,通過連接硬件給硬件發(fā)送指令以完成一些動作的藍牙開發(fā)都是基于中心模式(central)模式的開發(fā),也就是說我們開發(fā)的app是中心兼蕊,我們要連接的硬件是外設初厚。如果需要其他設備連接手機藍牙,并對手機進行一些操作孙技,那就是基于外設模式(peripheral)的開發(fā)产禾。 本次我們主要介紹的就是中心模式的藍牙開發(fā)

  • 設備簡介
    中心設備(CBCentralManager):iOS系統(tǒng)的手機等設備
    外圍設備(CBPeripheral):手環(huán)等第三方設備

  • 藍牙數(shù)據(jù)傳輸簡介
    將外圍設備(車輛)的數(shù)據(jù)傳送給中心設備(手機)時, 數(shù)據(jù)是經(jīng)過兩層包裝的
    第一層是 Service(服務) , 可以是一個或多個, 比如車輛數(shù)據(jù)(服務)
    第二層是 Characteristic(特征) , 他提供了更多關于Service(服務)的數(shù)據(jù), 例如車輛數(shù)據(jù)(服務)中包含了兩個數(shù)據(jù), 分別是里程數(shù)據(jù)和續(xù)航數(shù)據(jù), 這兩個就是車輛數(shù)據(jù)(服務)的具體數(shù)據(jù)(特征)

  • 具體操作簡介
    讀(read) , 寫(write) , 訂閱(notify)
    我們的目的是讀取設備中的數(shù)據(jù)(read) , 或者給設備寫入一定的數(shù)據(jù)(write)。有時候我們還想設備的數(shù)據(jù)變化的時候不需要我們手動去讀取這個值绪杏,需要設備自動通知我們它的值變化了下愈,值是多少。把值告訴app蕾久,這個時候就需要訂閱這個特征了(notify)

  • 其他相關概念

  1. 目前都使用的是低功耗藍牙4.0势似,藍牙外設必需為4.0及以上(2.0需要MFI認證),否則無法開發(fā),藍牙4.0設施由于低耗電履因,所以也叫做BLE障簿。
  2. CoreBluetooth框架的核心其實是兩個東西,peripheral和central, 能了解成外設和中心栅迄,就是你的蘋果手機就是中心站故,外部藍牙稱為外設。
  3. 服務和特征(service characteristic):簡而言之毅舆,外部藍牙中它有若干個服務service(服務你能了解為藍牙所擁有的可以力)西篓,而每個服務service下?lián)碛腥舾蓚€特征characteristic(特征你能了解為解釋這個服務的屬性)。
  4. Descriptor(形容)使用來形容characteristic變量的屬性憋活。例如岂津,一個descriptor能規(guī)定一個可讀的形容,或者者一個characteristic變量可接受的范圍悦即,或者者一個characteristic變量特定的單位吮成。
  5. 我們用的藍牙板塊是在淘寶買的, 大概十多元一個, ios大概每次能接受90個字節(jié), 安卓大概每次能接收20個字節(jié), 具體數(shù)字可可以會浮動, 應該是與藍牙板塊有關。

2. 藍牙連接

自己實踐的兩個藍牙demo:

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

2.1 CoreBluetooth框架

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

在這里插入圖片描述
  • 這兩組api分別對應不同的業(yè)務場景,左側(cè)叫做中心模式作瞄,就是以你的app作為中心茶宵,連接其他的外設的場景,而右側(cè)稱為外設模式粉洼,使用手機作為外設別其他中心設備操作的場景节预。
  • 服務和特征,特征的屬性(service and characteristic):
    每個設備都會有一些服務属韧,每個服務里面都會有一些特征安拟,特征就是具體鍵值對,提供數(shù)據(jù)的地方宵喂。每個特征屬性分為這么幾種:讀糠赦,寫,通知這么幾種方式锅棕。
//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. 創(chuàng)建中心設備(CBCentralManager)
  2. 中心設備開始掃描(scanForPeripherals)
  3. 掃描到外圍設備之后, 自動調(diào)用中心設備的代理方法(didDiscoverPeripheral)
  4. 如果設備過多, 可以將掃描到的外圍設備添加到數(shù)組
  5. 開始連接, 從數(shù)組中過濾出自己想要的設備, 進行連接(connectPeripheral)
  6. 連接上之后, 自動調(diào)用中心設備的代理方法(didConnectPeripheral), 在代理中, 進行查找外圍設備的服務(peripheral.discoverServices)
  7. 查找到服務之后, 自動調(diào)用外圍設備的代理(didDiscoverServices), 可通過UUID,查找具體的服務,查找服務(discoverCharacteristics)
  8. 查找到特征之后, 自動調(diào)用外圍設備的代理(didDiscoverCharacteristics), 通過UUID找到自己想要的特征, 讀取特征(readValueForCharacteristic)
  9. 讀取到特征之后, 自動調(diào)用外設的代理方法(didUpdateValueForCharacteristic),在這里打印或者解析自己想要的特征值.

2.4 藍牙中心模式裸燎,外設模式

2.4.1 藍牙中心模式

  • 中心模式流程
  1. 建立中心角色

  2. 掃描外設(discover)

  3. 連接外設(connect)

  4. 掃描外設中的服務和特征(discover)
    4.1 獲取外設的services
    4.2 獲取外設的Characteristics,獲取Characteristics的值顾瞻,獲取Characteristics的 Descriptor和Descriptor的值

  5. 與外設做數(shù)據(jù)交互(explore and interact)

  6. 訂閱Characteristic的通知

  7. 斷開連接(disconnect)

2.4.2 藍牙外設模式

  • 藍牙外設模式流程
  1. 啟動一個Peripheral管理對象

  2. 本地Peripheral設置服務,特性,描述,權(quán)限等等

  3. Peripheral發(fā)送廣告

  4. 設置處理訂閱德绿、取消訂閱荷荤、讀characteristic退渗、寫characteristic的委托方法

2.5 藍牙設備狀態(tài)

  • 藍牙設備狀態(tài)
    待機狀態(tài)(standby):設備沒有傳輸和發(fā)送數(shù)據(jù),并且沒有連接到任何設
    廣播狀態(tài)(Advertiser):周期性廣播狀態(tài)
    掃描狀態(tài)(Scanner):主動尋找正在廣播的設備
    發(fā)起鏈接狀態(tài)(Initiator):主動向掃描設備發(fā)起連接蕴纳。
    主設備(Master):作為主設備連接到其他設備会油。
    從設備(Slave):作為從設備連接到其他設備。
  • 藍牙設備的五種工作狀態(tài)
    準備(standby)
    廣播(advertising)
    監(jiān)聽掃描(Scanning
    發(fā)起連接(Initiating)
    已連接(Connected)

2.6 藍牙連接代碼實現(xiàn)

  1. 初始化
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
  1. 搜索掃描外圍設備
/**
 *  --  初始化成功自動調(diào)用
 *  --  必須實現(xiàn)的代理古毛,用來返回創(chuàng)建的centralManager的狀態(tài)翻翩。
 *  --  注意:必須確認當前是CBCentralManagerStatePoweredOn狀態(tài)才可以調(diào)用掃描外設的方法:
 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");
            // 開始掃描周圍的外設。
            /*
             -- 兩個參數(shù)為Nil表示默認掃描所有可見藍牙設備稻薇。
             -- 注意:第一個參數(shù)是用來掃描有指定服務的外設嫂冻。然后有些外設的服務是相同的,比如都有FFF5服務塞椎,那么都會發(fā)現(xiàn)絮吵;而有些外設的服務是不可見的,就會掃描不到設備忱屑。
             -- 成功掃描到外設后調(diào)用didDiscoverPeripheral
             */
            [self.centralManager scanForPeripheralsWithServices:nil options:nil];
        }
            break;
        default:
            break;
    }
}

#pragma mark 發(fā)現(xiàn)外設
- (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:@"根據(jù)設備名過濾"]) {
                    [_deviceDic setObject:peripheral forKey:[peripheral name]];
                     // 停止掃描, 看需求決定要不要加
//                    [_centralManager stopScan];
                    // 將設備信息傳到外面的頁面(VC), 構(gòu)成掃描到的設備列表
                    if ([self.delegate respondsToSelector:@selector(dataWithBluetoothDic:)]) {
                        [self.delegate dataWithBluetoothDic:_deviceDic];
                    }
                }
            }
        }
    }
}

  1. 連接外圍設備
// 連接設備(.h中聲明出去的接口, 一般在點擊設備列表連接時調(diào)用)
- (void)connectDeviceWithPeripheral:(CBPeripheral *)peripheral
{
    [self.centralManager connectPeripheral:peripheral options:nil];
}



#pragma mark 連接外設--成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //連接成功后停止掃描,節(jié)省內(nèi)存
    [central stopScan];
    peripheral.delegate = self;
    self.peripheral = peripheral;
    //4.掃描外設的服務
    /**
     --     外設的服務暇昂、特征莺戒、描述等方法是CBPeripheralDelegate的內(nèi)容,所以要先設置代理peripheral.delegate = self
     --     參數(shù)表示你關心的服務的UUID急波,比如我關心的是"FFE0",參數(shù)就可以為@[[CBUUID UUIDWithString:@"FFE0"]].那么didDiscoverServices方法回調(diào)內(nèi)容就只有這兩個UUID的服務从铲,不會有其他多余的內(nèi)容,提高效率澄暮。nil表示掃描所有服務
     --     成功發(fā)現(xiàn)服務名段,回調(diào)didDiscoverServices
     */
    [peripheral discoverServices:@[[CBUUID UUIDWithString:@"你要用的服務UUID"]]];
    if ([self.delegate respondsToSelector:@selector(didConnectBle)]) {
       // 已經(jīng)連接
        [self.delegate didConnectBle];
    }
}



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


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

  1. 獲得外圍設備的服務
#pragma mark 發(fā)現(xiàn)服務回調(diào)
- (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 發(fā)現(xiàn)特征回調(diào)
/**
 --  發(fā)現(xiàn)特征后,可以根據(jù)特征的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"]]) {
 
            /**
             -- 讀取成功回調(diào)didUpdateValueForCharacteristic
             */
            self.characteristic = characteristic;
            // 接收一次(是讀一次信息還是數(shù)據(jù)經(jīng)常變實時接收視情況而定, 再決定使用哪個)
//            [peripheral readValueForCharacteristic:characteristic];
            // 訂閱, 實時接收
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
 
            // 發(fā)送下行指令(發(fā)送一條)
            NSData *data = [@"硬件工程師給我的指令, 發(fā)送給藍牙該指令, 藍牙會給我返回一條數(shù)據(jù)" dataUsingEncoding:NSUTF8StringEncoding];
            // 將指令寫入藍牙
                [self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        }
        /**
         -- 當發(fā)現(xiàn)characteristic有descriptor,回調(diào)didDiscoverDescriptorsForCharacteristic
         */
        [peripheral discoverDescriptorsForCharacteristic:characteristic];
    }
}


  1. 從外圍設備讀取數(shù)據(jù)
#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 - 中心讀取外設實時數(shù)據(jù)
- (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. 給外圍設備發(fā)送(寫入)數(shù)據(jù)

// 上文中發(fā)現(xiàn)特征之后, 發(fā)送下行指令的時候其實就是向藍牙中寫入數(shù)據(jù)
// 例:
// 發(fā)送檢查藍牙命令
- (void)writeCheckBleWithBle
{
    _style = 1;
    // 發(fā)送下行指令(發(fā)送一條)
    NSData *data = [@"硬件工程師提供給你的指令, 類似于5E16010203...這種很長一串" dataUsingEncoding:NSUTF8StringEncoding];
    [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}



#pragma mark 數(shù)據(jù)寫入成功回調(diào)
- (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{
    /**
     -- 斷開連接后回調(diào)didDisconnectPeripheral
     -- 注意斷開后如果要重新掃描這個外設信夫,需要重新調(diào)用[self.centralManager scanForPeripheralsWithServices:nil options:nil];
     */
    [self.centralManager cancelPeripheralConnection:self.peripheral];
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市卡啰,隨后出現(xiàn)的幾起案子静稻,更是在濱河造成了極大的恐慌,老刑警劉巖匈辱,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件振湾,死亡現(xiàn)場離奇詭異,居然都是意外死亡亡脸,警方通過查閱死者的電腦和手機押搪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門树酪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嵌言,你說我怎么就攤上這事嗅回。” “怎么了摧茴?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵绵载,是天一觀的道長。 經(jīng)常有香客問我苛白,道長娃豹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任购裙,我火速辦了婚禮懂版,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘躏率。我一直安慰自己躯畴,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布薇芝。 她就那樣靜靜地躺著蓬抄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪夯到。 梳的紋絲不亂的頭發(fā)上嚷缭,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音耍贾,去河邊找鬼阅爽。 笑死,一個胖子當著我的面吹牛荐开,可吹牛的內(nèi)容都是我干的付翁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼誓焦,長吁一口氣:“原來是場噩夢啊……” “哼胆敞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杂伟,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤移层,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赫粥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體观话,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年越平,在試婚紗的時候發(fā)現(xiàn)自己被綠了频蛔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灵迫。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晦溪,靈堂內(nèi)的尸體忽然破棺而出瀑粥,到底是詐尸還是另有隱情,我是刑警寧澤三圆,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布狞换,位于F島的核電站,受9級特大地震影響舟肉,放射性物質(zhì)發(fā)生泄漏修噪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一路媚、第九天 我趴在偏房一處隱蔽的房頂上張望黄琼。 院中可真熱鬧,春花似錦整慎、人聲如沸脏款。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弛矛。三九已至,卻和暖如春比然,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背周循。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工强法, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人湾笛。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓饮怯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親甘磨。 傳聞我的和親對象是個殘疾皇子薛窥,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容