隨著智能家具点楼、智能穿戴等的興起,藍(lán)牙開發(fā)應(yīng)用越來越廣泛悴品,有關(guān)藍(lán)牙方面的問題禀综,今天就給大家進(jìn)行詳細(xì)的講解郎哭,想要了解藍(lán)牙在iOS開發(fā)中的具體實(shí)現(xiàn),首先我們必須知道什么是藍(lán)牙菇存?
藍(lán)牙概念:
隨著藍(lán)牙低功耗技術(shù)BLE(Bluetooth Low Energy)的發(fā)展夸研,藍(lán)牙技術(shù)正在一步步成熟,如今的大部分移動(dòng)設(shè)備都配備有藍(lán)牙4.0依鸥,相比之前的藍(lán)牙技術(shù)耗電量大大降低亥至。從ios的發(fā)展史也不難看出蘋果目前對(duì)藍(lán)牙技術(shù)也是越來越關(guān)注,例如蘋果于2013年9月發(fā)布的iOS7就配備了iBeacon技術(shù)贱迟,這項(xiàng)技術(shù)完全基于藍(lán)牙傳輸姐扮。但是眾所周知蘋果的設(shè)備對(duì)于權(quán)限要求也是比較高的,因此在iOS中并不能像Android一樣隨意使用藍(lán)牙進(jìn)行文件傳輸(除非你已經(jīng)越獄)衣吠。知道什么是藍(lán)牙之后,那么在iOS中進(jìn)行藍(lán)牙傳輸應(yīng)用開發(fā)常用的框架有哪幾種呢?
藍(lán)牙在開發(fā)中的框架
GameKit.framework:iOS7之前的藍(lán)牙通訊框架茶敏,從iOS7開始過期,但是目前多數(shù)應(yīng)用還是基于此框架缚俏。
MultipeerConnectivity.framework:iOS7開始引入的新的藍(lán)牙通訊開發(fā)框架惊搏,用于取代GameKit。
CoreBluetooth.framework:功能強(qiáng)大的藍(lán)牙開發(fā)框架忧换,要求設(shè)備必須支持藍(lán)牙4.0恬惯。
因?yàn)镚ameKit.framework和MultipeerConnectivity.framework框架都只能在iOS設(shè)備之間進(jìn)行數(shù)據(jù)傳輸,這個(gè)框架最大的特點(diǎn)就是完全基于BLE4.0標(biāo)準(zhǔn)并且支持非iOS設(shè)備亚茬。當(dāng)前BLE應(yīng)用相當(dāng)廣泛酪耳,不再僅僅是兩個(gè)設(shè)備之間的數(shù)據(jù)傳輸,它還有很多其他應(yīng)用市場(chǎng)刹缝,例如室內(nèi)定位碗暗、無線支付、智能家居等等梢夯,這也使得CoreBluetooth成為當(dāng)前最熱門的藍(lán)牙技術(shù)言疗。
今天我們只講目前使用最多的一種藍(lán)牙框架CoreBluetooth.framework,在做藍(lán)牙開發(fā)之前厨疙,最好先了解一些概念:
外圍設(shè)備和中央設(shè)備在CoreBluetooth中使用CBPeripheralManager和CBCentralManager表示洲守。
CBPeripheralManager:外圍設(shè)備通常用于發(fā)布服務(wù)、生成數(shù)據(jù)沾凄、保存數(shù)據(jù)梗醇。外圍設(shè)備發(fā)布并廣播服務(wù),告訴周圍的中央設(shè)備它的可用服務(wù)和特征撒蟀。
CBCentralManager:中央設(shè)備使用外圍設(shè)備的數(shù)據(jù)叙谨。中央設(shè)備掃描到外圍設(shè)備后會(huì)就會(huì)試圖建立連接,一旦連接成功就可以使用這些服務(wù)和特征保屯。
外圍設(shè)備和中央設(shè)備之間交互的橋梁是服務(wù)(CBService)和特征(CBCharacteristic)手负,二者都有一個(gè)唯一的標(biāo)識(shí)UUID(CBUUID類型)來唯一確定一個(gè)服務(wù)或者特征涤垫,每個(gè)服務(wù)可以擁有多個(gè)特征,下面是他們之間的關(guān)系:
服務(wù)(services):藍(lán)牙外設(shè)對(duì)外廣播的必定會(huì)有一個(gè)服務(wù)竟终,可能也有多個(gè)蝠猬,服務(wù)下面包含著一些特征,服務(wù)可以理解成一個(gè)模塊的窗口统捶;
特征(characteristic):存在于服務(wù)下面的榆芦,一個(gè)服務(wù)下面也可以存在多個(gè)特征,特征可以理解成具體實(shí)現(xiàn)功能的窗口喘鸟,一般特征都會(huì)有value匆绣,也就是特征值,特征是與外界交互的最小單位什黑;
UUID:可以理解成藍(lán)牙上的唯一標(biāo)識(shí)符(硬件上肯定不是這個(gè)意思崎淳,但是這樣理解便于我們開發(fā)),為了區(qū)分不同的服務(wù)和特征愕把,或者給服務(wù)和特征取名字拣凹,我們就用UUID來代表服務(wù)和特征。
藍(lán)牙連接可以大致分為以下幾個(gè)步驟
1.創(chuàng)建一個(gè)Central Manage中心設(shè)備管理器
2.搜索外圍設(shè)備
3.連接外圍設(shè)備
4.獲得外圍設(shè)備的服務(wù)
5.獲得服務(wù)的特征
6.從外圍設(shè)備讀數(shù)據(jù)
7.給外圍設(shè)備發(fā)送數(shù)據(jù)
首先我們先導(dǎo)入系統(tǒng)的BLE的框架 #import <CoreBluetooth/CoreBluetooth.h>
必須遵守2個(gè)協(xié)議<CBCentralManagerDelegate, CBPeripheralDelegate>
/*中心管理者*/
@property(strong,nonatomic)CBCentralManager *CM;
/*連接到的外設(shè)*/
@property (nonatomic, strong)CBPeripheral *peripheral;
//1.創(chuàng)建一個(gè)Central Manage中心設(shè)備管理器并設(shè)置當(dāng)前控制器視圖為代理
_CM = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
#pragma mark - CBCentralManager代理方法
//2.中心服務(wù)器狀態(tài)更新后
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
NSString *message = nil;
switch (central.state) {
case CBManagerStateUnknown:
message = @"該設(shè)備藍(lán)牙狀態(tài)未知,請(qǐng)檢查系統(tǒng)設(shè)置";
break;
case CBManagerStateResetting:
message = @"該設(shè)備不支持藍(lán)牙功能,請(qǐng)檢查系統(tǒng)設(shè)置";
break;
case CBManagerStateUnsupported:
message = @"該設(shè)備不支持藍(lán)牙功能,請(qǐng)檢查系統(tǒng)設(shè)置";
break;
case CBManagerStateUnauthorized:
message = @"該設(shè)備藍(lán)牙未授權(quán),請(qǐng)檢查系統(tǒng)設(shè)置";
break;
case CBManagerStatePoweredOff:
message = @"該設(shè)備尚未打開藍(lán)牙,請(qǐng)?jiān)谠O(shè)置中打開";
break;
case CBManagerStatePoweredOn:
message = @"藍(lán)牙已經(jīng)成功開啟";
//開始掃描外圍設(shè)備
[self startScanPeripheralsWithServices];
break;
default:
message = @"該設(shè)備不支持藍(lán)牙功能或者該設(shè)備尚未打開藍(lán)牙,請(qǐng)檢查系統(tǒng)設(shè)置";
break;
}
if(message != nil && message.length != 0) {
[MBProgressHUD showSuccess:message toView:nil];
}
}
#pragma mark - 掃描外圍設(shè)備單獨(dú)寫一個(gè)方法
//掃描外圍設(shè)備方法
- (void)startScanPeripheralsWithServices {
//NO表示不會(huì)重復(fù)搜索已經(jīng)搜索到的設(shè)備
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
//當(dāng)?shù)弥{(lán)牙為開啟狀態(tài)時(shí)礼华,直接搜索周圍的藍(lán)牙設(shè)備
//開始搜索藍(lán)牙
[self.CM scanForPeripheralsWithServices:nil options:options];
}
//2.發(fā)現(xiàn)外設(shè)后調(diào)用的方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
/**
* 第一個(gè)參數(shù):中心管理者
* 第二個(gè)參數(shù):外圍設(shè)備
* 第三個(gè)參數(shù):外圍設(shè)備攜帶的數(shù)據(jù)
* 第四個(gè)參數(shù):外圍設(shè)備發(fā)出的藍(lán)牙信號(hào)強(qiáng)度
**/
//在此處對(duì)我們的advertisementData(外圍設(shè)備攜帶的廣播數(shù)據(jù))進(jìn)行一些處理
//把廣播包advertisementData里的參數(shù)kCBAdvDataManufacturerData數(shù)據(jù)拼接Mac地址
NSString *facturerDataString = [[[[advertisementData[@"kCBAdvDataManufacturerData"] description] stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *macAddress = [facturerDataString substringFromIndex:4];
/**
** 需要對(duì)連接到的外圍設(shè)備進(jìn)行過濾(外圍設(shè)備的名稱或者M(jìn)ac地址或者能區(qū)分的標(biāo)示UUID都可以)
** 1.信號(hào)強(qiáng)度(40以上才連接,80以上連接)
** 2.通過設(shè)備名(設(shè)備字符串前綴是 OBand)
** 我們的過濾規(guī)則自定義:譬如:有******前綴并且信號(hào)強(qiáng)度大于**
** if ([peripheral.name hasPrefix:@"Tv221u-169E665D"])
** 通過打印,我們知道RSSI一般是帶-的
**/
//此處設(shè)置需要的過濾規(guī)則
if ([macAddress isEqualToString:@"04a3169e665d"]) {
[self.CM stopScan];
//通常通過過濾,我們會(huì)得到一些外設(shè),然后將外圍設(shè)備儲(chǔ)存到我們的可變數(shù)組中
//將符合要求的設(shè)備進(jìn)行數(shù)據(jù)持久化,以便下面連接的時(shí)候使用
self.peripheral = peripheral;
//發(fā)現(xiàn)完之后就是進(jìn)行連接
[self.CM connectPeripheral:self.peripheral options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
}
}
//3.連接外圍設(shè)備
//中心管理者連接外圍設(shè)備成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
/**
* 第一個(gè)參數(shù):中心管理者
* 第二個(gè)參數(shù):外圍設(shè)備
**/
//連接成功后,設(shè)置外圍設(shè)備的代理
self.peripheral.delegate = self;
//中心管理者連接外圍設(shè)備成功后停止掃以達(dá)到節(jié)省電量的目的
[self.CM stopScan];
//外圍設(shè)備發(fā)現(xiàn)服務(wù),傳nil代表不過濾
//這里會(huì)觸發(fā)外圍設(shè)備的代理方法
//- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
[self.peripheral discoverServices:nil];
}
//外圍設(shè)備連接失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
[MBProgressHUD showSuccess:@"外圍設(shè)備連接失敗" toView:nil];
}
//丟失連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
[MBProgressHUD showSuccess:@"外圍設(shè)備丟失連接" toView:nil];
}
//4.獲得外圍設(shè)備的服務(wù)
#pragma mark - CBPeripheral Delegate
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error? {
if (error) {
[MBProgressHUD showSuccess:@"搜索服務(wù)時(shí)發(fā)生錯(cuò)誤" toView:nil];
DLog(@"搜索服務(wù)%@時(shí)發(fā)生錯(cuò)誤:%@", peripheral.name, [error localizedDescription]);
return;
}
if ([peripheral isEqual:self.peripheral]) {
//便利獲得的外圍設(shè)備的服務(wù)
for (CBService *services in peripheral.services) {
[peripheral discoverCharacteristics:nil forService:services];
}
}
}
//5.獲得服務(wù)的特征值
//發(fā)現(xiàn)外圍設(shè)備服務(wù)里的特征值的時(shí)候調(diào)用的代理方法(這個(gè)是比較重要的方法,你在這里可以通過事先知道的UUID找到你需要的特征值咐鹤、訂閱特征,或者這里寫入數(shù)據(jù)給特征也可以)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error {
if (error) {
[MBProgressHUD showSuccess:@"搜索特征時(shí)發(fā)生錯(cuò)誤" toView:nil];
DLog(@"搜索特征%@時(shí)發(fā)生錯(cuò)誤:%@", service.UUID, [error localizedDescription]);
return;
}
if ([peripheral isEqual:self.peripheral]) {
//新建特征值數(shù)組
NSArray *characteristics = [service characteristics];
CBCharacteristic *characteristic;
?if ([[service UUID] isEqual:[CBUUID UUIDWithString:@"aaaaaaaaaa"]]) {
for (characteristic in characteristics) {
//發(fā)現(xiàn)特征
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:@"bbbbbbbbbbb"]]) {
//記錄特征
_cbchar = characteristic;
}
}
}
}
}
//訂閱的特征值發(fā)生改變會(huì)調(diào)用此方法
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
NSData *data = characteristic.value;
NSString *characteristicValue = [[[[characteristic.value description] stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
//根據(jù)藍(lán)牙響應(yīng)的數(shù)據(jù)去判斷向特征下的藍(lán)牙發(fā)送命令
if ([str isEqualToString:@"c c c c c c c c c c"]) {
if ([_cbchar.UUID isEqual:[CBUUID UUIDWithString:@"FFE9"]]) {
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];
int8_t byte0 = 0x7B;
[data appendBytes:&byte0 length:sizeof(byte0)];
int8_t byte1 = 0x00;
[data appendBytes:&byte1 length:sizeof(byte1)];
int8_t byte2 = 0x00;
[data appendBytes:&byte2 length:sizeof(byte2)];
int8_t byte3 = 0x01;
[data appendBytes:&byte3 length:sizeof(byte3)];
int8_t byte4 = 0x00;
[data appendBytes:&byte4 length:sizeof(byte4)];
int8_t byte5 = 0xFF;
[data appendBytes:&byte5 length:sizeof(byte5)];
int8_t byte6 = 0x7D;
[data appendBytes:&byte6 length:sizeof(byte6)];
//發(fā)送命令
[_peripheral writeValue:data forCharacteristic:_cbchar type:CBCharacteristicWriteWithResponse];
}
}else if ([str isEqualToString:@"dddddddddddd"]) {
if ([_cbchar.UUID isEqual:[CBUUID UUIDWithString:@"1111111111"]]) {
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];
int8_t byte0 = 0x7B;
[data appendBytes:&byte0 length:sizeof(byte0)];
int8_t byte1 = 0x00;
[data appendBytes:&byte1 length:sizeof(byte1)];
int8_t byte2 = 0x00;
[data appendBytes:&byte2 length:sizeof(byte2)];
int8_t byte3 = 0x01;
[data appendBytes:&byte3 length:sizeof(byte3)];
int8_t byte4 = 0x01;
[data appendBytes:&byte4 length:sizeof(byte4)];
int8_t byte5 = 0xFE;
[data appendBytes:&byte5 length:sizeof(byte5)];
int8_t byte6 = 0x7D;
[data appendBytes:&byte6 length:sizeof(byte6)];
//發(fā)送命令
[_peripheral writeValue:data forCharacteristic:_cbchar type:CBCharacteristicWriteWithResponse];
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
DLog(@"Error writing characteristic value: %@",
[error localizedDescription]);
}else {
DLog(@"發(fā)送數(shù)據(jù)成功");
[peripheral readValueForCharacteristic:characteristic];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
DLog(@"更新特征值數(shù)據(jù): %@ 錯(cuò)誤: %@", characteristic.UUID,[error localizedDescription]);
}
//發(fā)送命令
if ([characteristicValue isEqualToString:@"123456789"]) {
//狀態(tài)
}else if ([characteristicValue isEqualToString:@"987654321"]) {
//狀態(tài)
}
}
補(bǔ)充說明:一個(gè)設(shè)備可以擁有不同的服務(wù)拗秘,一個(gè)服務(wù)下面可以對(duì)應(yīng)不同的特征值 總體思路就是去找某一個(gè)服務(wù)下的某一個(gè)特征值圣絮,發(fā)送不同命令去操作 具體命令看硬件那邊,iOS 不同安卓雕旨,mac地址一開始不像安卓那樣直接可以獲取扮匠,想要獲取就要把地址放到廣播包advertisementData里
如果有不明白的可以去看蘋果官方API,解釋的很清楚