公司的一個項目提了一些需求拯爽, 其中有一項是需要集成藍(lán)牙功能蔫磨。上一次做藍(lán)牙功能已經(jīng)是剛?cè)胄械臅r候,于是乘著周末休息的把藍(lán)牙的知識復(fù)習(xí)復(fù)習(xí)碰凶, 也給大家簡單說明一下藍(lán)牙的掃描和連接缭受。
1胁澳、藍(lán)牙的集中連接方式
2、iOS藍(lán)牙開發(fā)的關(guān)鍵詞
3米者、CoreBluetooth框架
4韭畸、代碼實現(xiàn)
一、iOS設(shè)備藍(lán)牙連接主要有一下幾種實現(xiàn)方式:
①.參加蘋果的(MFI)計劃, 也就是需要得到蘋果的認(rèn)證, 費用高, 有這方面需求的可以自己去了解蔓搞。
②.CoreBluetooth框架. 只支持4.0的藍(lán)牙設(shè)備, 這是我們iOS從業(yè)人員使用最多的一種方法胰丁。
③.GameKit框架. 這個框架只用用于iOS設(shè)備之間的藍(lán)牙連接通訊, 并不符合我們的需求, 沒有仔細(xì)研究.
④.私有API(BluetoothManager框架). 公司之前的項目有用過這個,最后項目是用的企業(yè)包托管在蒲公英上喂分,不過新項目并不準(zhǔn)備使用私有庫锦庸,略過。
⑤.越獄. 如果你做越獄包得話,這個就隨意了, 想怎么用怎么用蒲祈。
二甘萧、iOS藍(lán)牙開發(fā)的關(guān)鍵詞
中心設(shè)備(中心設(shè)備):就是用來掃描周圍藍(lán)牙硬件的設(shè)備,比如通過你手機(jī)的藍(lán)牙來掃描并連接智能手環(huán)梆掸,這時候你的手機(jī)就是中心設(shè)備扬卷。
外設(shè)(Peripheral):被掃描的設(shè)備。比如當(dāng)你用手機(jī)的藍(lán)牙掃描連接智能手環(huán)的時候酸钦,智能手環(huán)就是外設(shè)怪得。
服務(wù)(services):外設(shè)廣播和運行的時候會有服務(wù),可以理解成一個功能模塊卑硫,中心設(shè)備可以讀取服務(wù)徒恋。一個外設(shè)可以有多個服務(wù)。
特征(characteristic):在服務(wù)中的一個單位欢伏,一個服務(wù)可以有多個特征入挣,特征會有一個value,一般讀寫的數(shù)據(jù)就是這個value颜懊。
UUID :UUID在這里有多種意思财岔。設(shè)備自身有硬件的UUID,不同的中心設(shè)備連接到同一個外設(shè)會顯示不同的UUID河爹,此外設(shè)發(fā)送的每個服務(wù)也都有自己的UUID,每個服務(wù)中的特征也有自己所屬的UUID桐款。
下面是一張iOS藍(lán)牙的架構(gòu)圖咸这,可以作為參照。
三魔眨、CoreBluetooth框架
如上圖所示媳维,iOS中的藍(lán)牙開發(fā)框架CoreBluetooth處在藍(lán)牙低功耗協(xié)議棧的上面酿雪,我們開發(fā)的時候只是使用CoreBluetooth這個框架,通過CoreBluetooth可以輕松實現(xiàn)外設(shè)或中心設(shè)備的開發(fā)侄刽。
CoreBluetooth可以分為兩大模塊指黎,中心設(shè)備central,外設(shè)peripheral州丹,它們倆各有自己的一套API供我們使用醋安。
上圖左邊的就是中心設(shè)備的開發(fā)類,我們平時是使用CBCentralManager來進(jìn)行相關(guān)操作墓毒。
CBCentralManager: 藍(lán)牙中心設(shè)備管理類吓揪,用來統(tǒng)一調(diào)度中心設(shè)備的開發(fā)
CBPeripheral :藍(lán)牙外設(shè),例如藍(lán)牙手環(huán)所计、心率監(jiān)測儀柠辞。
CBService :藍(lán)牙外設(shè)的服務(wù),可以有0個或者多個服務(wù)主胧。
CBCharacteristic :服務(wù)中的特征叭首,每一個藍(lán)牙服務(wù)中可以有0個或多個特征,特征中包含數(shù)據(jù)信息踪栋。
CBUUID:可以理解為服務(wù)或特征的身份證焙格,可以用來選擇需要的服務(wù)和特征。
右邊是外設(shè)開發(fā)相關(guān)類己英,一般是圍繞著CBPeripheralManager來進(jìn)行編碼间螟。
右邊是外設(shè)開發(fā)相關(guān)類,一般是圍繞著CBPeripheralManager來進(jìn)行編碼损肛。
CBPeripheralManager: 藍(lán)牙外設(shè)開發(fā)時使用厢破,用來開發(fā)藍(lán)牙外設(shè)的中心管理類。
CBCentral:藍(lán)牙中心設(shè)備治拿,例如用來連接藍(lán)牙手環(huán)的手機(jī)摩泪。
CBMutableService:外設(shè)開發(fā)的時候可以添加多個服務(wù),所有這里用CBMutableService來創(chuàng)建添加服務(wù)劫谅。
CBMutableCharacteristic:每個服務(wù)中可以有多個特征见坑,外設(shè)開發(fā)給服務(wù)添加特征的時候使用這個類。
CBATTRequest:讀或者寫請求捏检。它的實例對象有一個value屬性荞驴,用來裝載外設(shè)進(jìn)行藍(lán)牙讀取或?qū)懭胝埱髸r的數(shù)據(jù)。一般在外設(shè)寫入或讀取的回調(diào)方法中有這一個參數(shù)贯城。
四熊楼、代碼實現(xiàn)
1、首先導(dǎo)入CoreBluetooth框架能犯,并遵守協(xié)議
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController () <CBCentralManagerDelegate,CBPeripheralDelegate>
2鲫骗、創(chuàng)建外設(shè)管理對象犬耻,用一個屬性來強(qiáng)引用這個對象。并且在創(chuàng)建的時候設(shè)置代理执泰,聲明放到哪個線程枕磁。
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
// 創(chuàng)建外設(shè)管理器,會回調(diào)peripheralManagerDidUpdateState方法
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
3术吝、當(dāng)創(chuàng)建CBPeripheralManager的時候计济,會回調(diào)判斷藍(lán)牙狀態(tài)的方法。當(dāng)藍(lán)牙狀態(tài)沒問題的時候創(chuàng)建外設(shè)的Service(服務(wù))和Characteristics(特征)顿苇。
/** 判斷手機(jī)藍(lán)牙狀態(tài)
CBManagerStateUnknown = 0, 未知
CBManagerStateResetting, 重置中
CBManagerStateUnsupported, 不支持
CBManagerStateUnauthorized, 未驗證
CBManagerStatePoweredOff, 未啟動
CBManagerStatePoweredOn, 可用
*/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
// 藍(lán)牙可用峭咒,開始掃描外設(shè)
if (central.state == CBManagerStatePoweredOn) {
NSLog(@"藍(lán)牙可用");
// 第一個參數(shù)為nil表掃描所有藍(lán)牙設(shè)備
[central scanForPeripheralsWithServices:nil options:nil];
}
if(central.state==CBManagerStateUnsupported) {
NSLog(@"該設(shè)備不支持藍(lán)牙");
}
if (central.state==CBManagerStatePoweredOff) {
NSLog(@"藍(lán)牙已關(guān)閉");
}
}
4、當(dāng)掃描到外設(shè)之后纪岁,就會回調(diào)下面這個方法凑队,可以在這個方法中繼續(xù)設(shè)置篩選條件,例如根據(jù)外設(shè)名字的前綴來選擇幔翰,如果符合條件就進(jìn)行連接漩氨。
/** 發(fā)現(xiàn)符合要求的外設(shè),回調(diào) */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI {
// 對外設(shè)對象進(jìn)行強(qiáng)引用
self.peripheral = peripheral;
//打印外設(shè)的UUID
NSLog(@"===%@",peripheral.identifier);
//打印外設(shè)的名稱
NSLog(@"===%@",peripheral.name);
//根據(jù)外設(shè)名字來過濾外設(shè)遗增,我的外設(shè)名字叫ble_mp008
if ([peripheral.name hasPrefix:@"ble"]) {
[central connectPeripheral:peripheral options:nil];
}
}
5叫惊、當(dāng)連接成功的時候,就會來到下面這個方法做修。為了省電霍狰,當(dāng)連接上外設(shè)之后,就讓中心設(shè)備停止掃描,并且別忘記設(shè)置連接上的外設(shè)的代理。在這個方法里根據(jù)UUID進(jìn)行服務(wù)的查找脯丝。
/** 連接成功 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
// 可以停止掃描
[self.centralManager stopScan];
// 設(shè)置代理
peripheral.delegate = self;
// 根據(jù)UUID來尋找服務(wù),我的設(shè)備服務(wù)UUDI是 “FF00”
[peripheral discoverServices:@[[CBUUID UUIDWithString:@"FF00"]]];
NSLog(@"連接成功");
}
6宾濒、連接失敗和斷開連接也有各自的回調(diào)方法。在斷開連接的時候屏箍,我們可以設(shè)置自動重連绘梦,根據(jù)項目需求來自定義里面的代碼。
/** 連接失敗的回調(diào) */
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"連接失敗");
}
/** 斷開連接 */
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
NSLog(@"斷開連接");
// 斷開連接可以設(shè)置重新連接
[central connectPeripheral:peripheral options:nil];
}
7赴魁、下面開始處理代理方法卸奉。最開始就是發(fā)現(xiàn)服務(wù)的方法。這個方法里可以遍歷服務(wù)颖御,找到需要的服務(wù)择卦。我這里選擇第一個服務(wù)。
找到服務(wù)之后郎嫁,連貫的動作繼續(xù)根據(jù)特征的UUID尋找服務(wù)中的特征秉继。
/** 發(fā)現(xiàn)服務(wù) */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
// 遍歷出外設(shè)中所有的服務(wù)
for (CBService *service in peripheral.services) {
NSLog(@"所有的服務(wù):%@",service);
}
// 這里僅有一個服務(wù),所以直接獲取
CBService *service = peripheral.services.lastObject;
// 根據(jù)UUID尋找服務(wù)中的特征
[peripheral discoverCharacteristics:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service];
}
8泽铛、下面這個方法里做的事情不少尚辑。
當(dāng)發(fā)現(xiàn)特征之后,與服務(wù)一樣可以遍歷特征盔腔,根據(jù)外設(shè)開發(fā)人員給的文檔找出不同特征杠茬,做出相應(yīng)的操作。
我這里獲取第一個特征弛随。
再重復(fù)一遍瓢喉,一般開發(fā)中可以設(shè)置兩個特征,一個用來發(fā)送數(shù)據(jù)舀透,一個用來接收中心設(shè)備寫過來的數(shù)據(jù)栓票。
這里用一個屬性引用特征,是為了后面通過這個特征向外設(shè)寫入數(shù)據(jù)或發(fā)送指令愕够。
readValueForCharacteristic方法是直接讀一次這個特征上的數(shù)據(jù)走贪。
/** 發(fā)現(xiàn)特征回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
// 遍歷出所需要的特征
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"所有特征:%@", characteristic);
// 從外設(shè)開發(fā)人員那里拿到不同特征的UUID,不同特征做不同事情惑芭,比如有讀取數(shù)據(jù)的特征坠狡,也有寫入數(shù)據(jù)的特征
}
// 這里只獲取一個特征,寫入數(shù)據(jù)的時候需要用到這個特征
self.characteristic = service.characteristics.firstObject;
// 直接讀取這個特征數(shù)據(jù)遂跟,會調(diào)用didUpdateValueForCharacteristic
[peripheral readValueForCharacteristic:self.characteristic];
// 訂閱通知
[peripheral setNotifyValue:YES forCharacteristic:self.characteristic];
}
setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic方法是對這個特征進(jìn)行訂閱逃沿,訂閱成功之后,就可以監(jiān)控外設(shè)中這個特征值得變化了幻锁。
9凯亮、當(dāng)訂閱的狀態(tài)發(fā)生改變的時候,下面的方法就派上用場了越败。
/** 訂閱狀態(tài)的改變 */
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"訂閱失敗");
NSLog(@"%@",error);
}
if (characteristic.isNotifying) {
NSLog(@"訂閱成功");
} else {
NSLog(@"取消訂閱");
}
}
10触幼、外設(shè)可以發(fā)送數(shù)據(jù)給中心設(shè)備,中心設(shè)備也可以從外設(shè)讀取數(shù)據(jù)究飞,當(dāng)發(fā)生這些事情的時候置谦,就會回調(diào)這個方法。通過特種中的value屬性拿到原始數(shù)據(jù)亿傅,然后根據(jù)需求解析數(shù)據(jù)媒峡。
/** 接收到數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
// 拿到外設(shè)發(fā)送過來的數(shù)據(jù)
NSData *data = characteristic.value;
}
11、中心設(shè)備可以向外設(shè)寫入數(shù)據(jù)葵擎,也可以向外設(shè)發(fā)送請求或指令谅阿,當(dāng)需要進(jìn)行這些操作的時候該怎么辦呢。
首先把要寫入的數(shù)據(jù)轉(zhuǎn)化為NSData格式,然后根據(jù)上面拿到的寫入數(shù)據(jù)的特征签餐,運用方法writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type來進(jìn)行數(shù)據(jù)的寫入寓涨。
當(dāng)寫入數(shù)據(jù)的時候,系統(tǒng)也會回調(diào)這個方法peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
/** 寫入數(shù)據(jù) */
// 用NSData類型來寫入
NSData *data = [要寫入的值 dataUsingEncoding:NSUTF8StringEncoding];
// 根據(jù)上面的特征self.characteristic來寫入數(shù)據(jù)
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
/** 寫入數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
NSLog(@"寫入成功");
}
12氯檐、中心設(shè)備如何主動從外設(shè)讀取數(shù)據(jù)呢戒良。
用正在連接的外設(shè)對象來調(diào)用readValueForCharacteristic方法,并且把將要讀取數(shù)據(jù)的特征作為參數(shù)冠摄,這樣就可以主動拿一次數(shù)據(jù)了糯崎。
去到第10步的回調(diào)方法中,在特征的value屬性中拿到這次的數(shù)據(jù)河泳。
[self.peripheral readValueForCharacteristic:self.characteristic];
后記
到此為止就是一套iOS藍(lán)牙讀寫的流程沃呢,具體細(xì)化的東西也許就需要硬件工程師或者其他工作人員的配合,比如我們這就就提供好了相應(yīng)的SDK進(jìn)行后續(xù)操作拆挥,這些就不寫出來了薄霜。