最近幾個(gè)月都在做藍(lán)牙的項(xiàng)目跃闹,趁現(xiàn)在有空次乓,就把在藍(lán)牙開發(fā)過程中的心得和踩過的坑給記錄下來疆液,分享給大家颓鲜,避免大家在藍(lán)牙開發(fā)過程中能避免踩相同的坑表窘。
網(wǎng)上關(guān)于藍(lán)牙開發(fā)的原理知識(shí)都有了,這里就不在重復(fù)累贅甜滨,只簡單說一下實(shí)現(xiàn)過程邏輯:
- 首先需要在info.plist添加請求藍(lán)牙功能的權(quán)限“NSBluetoothPeripheralUsageDescription”
- 創(chuàng)建全局藍(lán)牙管理類
- 用藍(lán)牙管理類進(jìn)行掃描
- 從掃描的結(jié)果中找到你的藍(lán)牙外設(shè)蚊丐,進(jìn)行手動(dòng)或自動(dòng)連接
- 連接成功收掃描外設(shè)的服務(wù)和特征,每個(gè)服務(wù)都包含一個(gè)特征的數(shù)組艳吠,這些不同的特征就是用來跟藍(lán)牙外設(shè)進(jìn)行數(shù)據(jù)通信的對象麦备,特征一般分兩種,一種是可讀characteristic_notify昭娩,一種是可讀寫characteristic_readWrite凛篙。這兩個(gè)特征藍(lán)牙硬件工程師會(huì)告訴你,你只需要找到這兩個(gè)特征用私有變量保存起來即可栏渺,然后把可讀特征characteristic_notify設(shè)置為notify呛梆,你就能收到藍(lán)牙外設(shè)發(fā)送過來的實(shí)時(shí)數(shù)據(jù)。characteristic_readWrite特征就是用來發(fā)送數(shù)據(jù)了磕诊。
接下來看一下代碼實(shí)現(xiàn):
一. 定義全局藍(lán)牙管理類
- 這個(gè)類負(fù)責(zé)藍(lán)牙的掃描填物,停止掃描,連接霎终,斷開連接
它有一個(gè)代理CBCentralManagerDelegate滞磺,必須實(shí)現(xiàn)它的centralManagerDidUpdateState方法,用來返回藍(lán)牙功能打開和關(guān)閉的狀態(tài)
self.centralManage = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
這句代碼實(shí)現(xiàn)后會(huì)自動(dòng)判斷你的設(shè)備有沒有打開藍(lán)牙功能莱褒,如果沒有回自動(dòng)彈框讓你去設(shè)置里打開開關(guān)击困。
- 然后我們在centralManagerDidUpdateState代理中實(shí)現(xiàn)藍(lán)牙掃描:
#pragma mark - CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
if (central.state == CBCentralManagerStatePoweredOn) {
[self.centralManage scanForPeripheralsWithServices:nil options:nil];
}else{
[MBProgressHUD wj_showPlainText:@"藍(lán)牙已關(guān)閉" view:nil];
}
}
- 掃描結(jié)果會(huì)在didDiscoverPeripheral返回
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
NSData *data = advertisementData[@"kCBAdvDataManufacturerData"];
DLog(@"廣播數(shù)據(jù)---------------%@",data);
if ([peripheral.name isEqualToString:xxx]) {
// 這里找到你匹配的外設(shè)名字
[self.centralManage connectPeripheral:peripheral options:nil];
}
NSString *dataStr = [FGDataChangeTool hexadecimalString:data];
if ([dataStr isEqualToString:xxx]) {
// 這里找到外設(shè)給你發(fā)的廣播數(shù)據(jù)匹配
[self.centralManage connectPeripheral:peripheral options:nil];
}
}
注意:上面實(shí)例代碼中的兩個(gè)if判斷條件為非必須,這個(gè)需要根據(jù)你的業(yè)務(wù)需求广凸,比如可以根據(jù)你的外設(shè)名稱進(jìn)行匹配(不靠譜阅茶,因?yàn)槊挚勺儯⒒蛘咦屇愕乃{(lán)牙硬件工程師發(fā)一個(gè)標(biāo)識(shí)碼用來區(qū)分谅海。
隨便說一句 *由于iOS藍(lán)牙開發(fā)不能直接獲取藍(lán)牙的Mac地址脸哀,如果需要用到Mac地址作為外設(shè)的唯一標(biāo)識(shí)碼,只能讓硬件工程師把Mac地址用廣播發(fā)送過來扭吁,字段還是用“ kCBAdvDataManufacturerData” *
4.1. 連接后撞蜂,會(huì)走didConnectPeripheral代理方法盲镶,在這個(gè)方法里面需要把peripheral藍(lán)牙外設(shè)對象設(shè)置一下代理CBPeripheralDelegate,并且實(shí)現(xiàn)對服務(wù)的掃描:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
// 4.對外設(shè)進(jìn)行掃描
peripheral.delegate = self;
[peripheral discoverServices:nil];
}
4.2. 斷開連接,用戶主動(dòng)斷開或者代碼執(zhí)行斷開連接都會(huì)執(zhí)行這個(gè)方法:
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
[MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
[MBProgressHUD wj_showError:@"藍(lán)牙已斷開連接" toView:nil];
}
4.3 連接失斄律恪:
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
[MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
[MBProgressHUD wj_showError:@"連接失敗,請重試" toView:nil];
}
二. 前面4.1我們進(jìn)行對服務(wù)掃描后徒河,如果成功沒什么問題就會(huì)走CBPeripheralDelegate代理方法didDiscoverServices發(fā)現(xiàn)服務(wù),返回的服務(wù)是多個(gè)送漠,每個(gè)服務(wù)又包含多個(gè)特征顽照,這里只需要把所有的服務(wù)遍歷然后掃描所有的特征即可。
#pragma mark - CBPeripheralDelegate
// 外設(shè)發(fā)現(xiàn)服務(wù)回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error{
// 保存私有變量peripheral
self.peripheral = peripheral;
for (CBService *service in peripheral.services) {
//4. 1對外設(shè)掃描到的服務(wù)進(jìn)行特征掃描
[self.peripheral discoverCharacteristics:nil forService:service];
}
[self beginRefresh];
//停止掃描
[self.centralManager stopScan];
}
掃描特征后會(huì)在代理方法返回所有的特征:
// 外設(shè)發(fā)現(xiàn)特征回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
DLog(@"特征---------%@",characteristic);
if ([characteristic.UUID.UUIDString isEqualToString:@"你的讀寫特征值"]) {
self.characteristic_write = characteristic;
}
else if ([characteristic.UUID.UUIDString isEqualToString:@"你的只讀特征值"]){
self.characteristic_notify = characteristic;
[self.peripheral setNotifyValue:YES forCharacteristic:self.characteristic_notify];
}
//電池特征值闽寡,注意打印的時(shí)候是顯示Battery Level而不是2A19代兵,但是代碼中需要用2A19進(jìn)行匹配。
else if ([characteristic.UUID.UUIDString isEqualToString:@"2A19"]){
self.characteristic_Battery = characteristic;
// 設(shè)置readValueForCharacteristic才能獲得電量值
[peripheral readValueForCharacteristic:characteristic];
// 設(shè)置setNotifyValue才能收到電量改變的通知
[self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
三. 藍(lán)牙發(fā)送的所有數(shù)據(jù)都會(huì)在這個(gè)代理方法didUpdateValueForCharacteristic返回一個(gè)NSData對象:
// 當(dāng)特征的值發(fā)生變化時(shí)回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
for (CBService *sever in peripheral.services) {
for (CBCharacteristic *character in sever.characteristics) {
if ([character.UUID.UUIDString isEqualToString:@"2A19"]) {
DLog(@"電量信息----------%@",character);
}
}
}
// 其他數(shù)據(jù)處理
[self dealWithData];
}
四. 上面的代碼基本已經(jīng)完成大部分的需求了爷狈,包括藍(lán)牙的連接植影,斷開,掃描設(shè)備涎永,保存特征值思币,如果需要進(jìn)行下一步的藍(lán)牙數(shù)據(jù)通訊,只需要用writeValue方法進(jìn)行發(fā)送即可羡微。發(fā)送后的同樣在上面的didUpdateValueForCharacteristic代理方法接收數(shù)據(jù)谷饿。
Byte *bytes = (Byte *)malloc(6);
bytes[0] = 0xaa;
bytes[1] = data1;
bytes[2] = data2;
bytes[3] = (Byte)(((bytes[1] & 0xff) + (bytes[2] & 0xff)) / 3);
bytes[4] = (Byte)(((bytes[1] & 0xff) + (bytes[2] & 0xff)) % 3);
bytes[5] = 0xdc;
NSData *sendData = [NSData dataWithBytes:sendByte length:6];
[self.peripheral writeValue:sendData forCharacteristic:self.characteristic type:0];
五. 在藍(lán)牙手法數(shù)據(jù)過程中需要對數(shù)據(jù)進(jìn)行解析,數(shù)據(jù)都是16進(jìn)制的NSData對象妈倔,這些數(shù)據(jù)可不是像HTTP通訊那樣簡單傳個(gè)Json對象給你解析就完事了博投,需要按照硬件工程師給你的協(xié)議進(jìn)行一一解析,解碼成看得懂的數(shù)據(jù)盯蝴。這里發(fā)一份我在工作中常用到的一些解析工具類毅哗,包括一些進(jìn)制間的轉(zhuǎn)化。喜歡的點(diǎn)個(gè)star捧挺,這是給我最好的鼓勵(lì)??
iOSBlueTool