簡介
最近做了一個智能門鎖利用藍(lán)牙與app交互的項目。整理一下相關(guān)藍(lán)牙知識。下面要講的是利用原生<CoreBluetooth.framework>框架封裝demo,且支持藍(lán)牙4.0。藍(lán)牙官方文檔
背景
藍(lán)牙分為兩種形式: 1)中心者模式 2)管理者模式,一般絕大部分我們都是使用第一種模式,中心者模式是我們手機(jī)作為主機(jī),連接藍(lán)牙外設(shè),而管理者模式是我們手機(jī)自己作為外設(shè),自己創(chuàng)建服務(wù)和特征,然后有其他的設(shè)備連接我們的手機(jī)。
接下來我們就是圍繞第一種模式:中心者模式來講解。
步驟
藍(lán)牙連接可以大致分為以下幾個步驟:
1.建立一個Central Manager實例進(jìn)行藍(lán)牙管理
2.搜索外圍設(shè)備
3.連接外圍設(shè)備
4.獲得外圍設(shè)備的服務(wù)
5.獲得服務(wù)的特征
6.從外圍設(shè)備讀數(shù)據(jù)、給外圍設(shè)備發(fā)送數(shù)據(jù)
簡言之:就是我們的app創(chuàng)建一個藍(lán)牙中心管理者對象,調(diào)用SDK的方法去搜索周圍可發(fā)現(xiàn)的設(shè)備,搜索成功并發(fā)現(xiàn)有可用的設(shè)備后,進(jìn)行連接,連接成功后再獲取設(shè)備的服務(wù)與特征,最后進(jìn)行數(shù)據(jù)的交互。
疑問:什么是服務(wù)?什么是特征?
下面用一張圖進(jìn)行講解~

簡言之:就是一個外設(shè)有幾個服務(wù),每一個服務(wù)又有幾個特征,我們可以通過服務(wù)判斷這個外設(shè)是不是我們要連接的外設(shè),通過特征獲取想要的數(shù)據(jù)。在特征有一個叫做UUID的屬性,這個屬性可以作為判斷該特征是否是我們想要的特征的依據(jù),這個跟硬件工程師要對接好UUID的值。
代碼
在.h文件中定義一些全局屬性:
//中心管理者
@property (nonatomic, strong) CBCentralManager *centralManager;
//外圍設(shè)備
@property (nonatomic, strong) CBPeripheral *peripheral;
//讀取設(shè)備信息的特征
@property (nonatomic, strong) CBCharacteristic *readCharteristic;
//收取消息的特征
@property (nonatomic, strong) CBCharacteristic *notifyCharteristic;
//發(fā)送消息的特征
@property (nonatomic, strong) CBCharacteristic *writeCharacteristic;
首先,我們要創(chuàng)建一個中心管理者單例
//queue中傳入nil默認(rèn)就是在主線程
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
//設(shè)置代理
self.centralManager.delegate = self;
實例化一個中心管理者之后,會自動調(diào)用下面代理方法:
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBManagerStatePoweredOff:{
NSLog(@"藍(lán)牙關(guān)閉");
break;
case CBManagerStatePoweredOn:{
NSLog(@"藍(lán)牙已打開");
[central scanForPeripheralsWithServices:nil options:nil];
}
break;
case CBManagerStateResetting:
break;
case CBManagerStateUnauthorized:
NSLog(@"系統(tǒng)藍(lán)未被授權(quán)");
break;
case CBManagerStateUnknown:
NSLog(@"系統(tǒng)藍(lán)牙當(dāng)前狀態(tài)不明確");
break;
case CBManagerStateUnsupported:
NSLog(@"系統(tǒng)藍(lán)牙設(shè)備不支持");
break;
default:
break;
}
}
這個代理方法是判斷系統(tǒng)的藍(lán)牙狀態(tài),如果藍(lán)牙已經(jīng)打開,則調(diào)用下面的代碼:
//在給services和options都傳入nil則是代表掃描所有條件的外圍設(shè)備
[central scanForPeripheralsWithServices:nil options:nil];
當(dāng)掃描到外圍設(shè)備后,會調(diào)用下面這個代理方法:(掃描到多少個外設(shè)就執(zhí)行多少次)
- (void)centralManager:(CBCentralManager *)central // 中心管理
didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral // 外設(shè)
advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData // 外設(shè)攜帶的數(shù)據(jù)
RSSI:(nonnull NSNumber *)RSSI// 外設(shè)發(fā)出的藍(lán)牙信號強(qiáng)度
{
//注意:這里可以獲取到掃描到外設(shè)的Mac地址,
NSString *mac = advertisementData[@"kCBAdvDataManufacturerData"]
//這里也可以過濾掉不需要的服務(wù)
//接下來可以把我們想要連接的Mac地址與掃描到的外設(shè)的Mac地址進(jìn)行匹配,如果一致就獲取
if ([self.peripheralMac isEqualToString:mac]) {
//這里就是我們要連接的外設(shè)
//獲取外部設(shè)備
self.peripheral = peripheral;
//用中心管理者去調(diào)用連接獲取到的外設(shè)的方法
[self.centralManager connectPeripheral:peripheral options:nil];
//停止搜索
[self.centralManager stopScan];
}
}
注意:代理方法是不會直接給我們返回外設(shè)的Mac地址的,我們可以通過advertisementData[@"kCBAdvDataManufacturerData"]去得到Mac地址,這個要跟硬件工程師溝通好怎么返回,以什么形式去返回。
連接成功后會自動執(zhí)行下面這個代理方法:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//獲取到已經(jīng)連接上的外設(shè)之后,設(shè)置代理
self.peripheral.delegate = self;
//用外設(shè)去獲取服務(wù)
[peripheral discoverServices:nil];
}
如果連接失敗,會調(diào)用下面這個方法:
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"連接失敗%@",error.localizedDescription);
}
如果兩個已經(jīng)上的設(shè)置突然斷開了連接,會自動調(diào)用下面的方法:
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
//這種情況一般是,你藍(lán)牙不穩(wěn)定或者藍(lán)牙斷開等一些外部環(huán)境問題
NSLog(@"已經(jīng)斷開藍(lán)牙連接");
{
連接外設(shè)成功并調(diào)用獲取外設(shè)服務(wù)的方法后,獲取外設(shè)服務(wù)成功后會調(diào)用下面的方法:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
for (CBService *service in peripheral.services){
NSLog(@"外設(shè)服務(wù)號:%@",service.UUID.UUIDString);
if ([service.UUID.UUIDString isEqualToString:kServiceUUID]) {
//外圍設(shè)備查找指定服務(wù)中的特征,characteristics為nil,表示尋找所有特征
[peripheral discoverCharacteristics:nil forService:service];
break;
}
}
}
當(dāng)獲取外設(shè)服務(wù)的特征成功后,自動執(zhí)行下面的代理方法:在這個方法中,就根據(jù)
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
//kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是與硬件對接好的特征的UUID,
for (CBCharacteristic *characteristic in service.characteristics)
{
//寫入特征
if ([characteristic.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID] ) {
self.writeCharacteristic = characteristic;
}
//通知特征
if ([characteristic.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID]) {
self.notifyCharteristic = characteristic;
/* 設(shè)置是否向特征訂閱數(shù)據(jù)實時通知,YES表示會實時多次會調(diào)用代理方法讀取數(shù)據(jù) */
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//讀取特征
if ([characteristic.UUID.UUIDString isEqualToString:kReadCharacteristicUUID]) {
self.readCharteristic = characteristic;
/* 讀取特征的數(shù)據(jù),調(diào)用此方法后會調(diào)用一次代理方法讀取數(shù)據(jù) */
[peripheral readValueForCharacteristic:characteristic];
}
}
}
注意:kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是與硬件對接好的特征的UUID,用來判斷是不是我們想要的特征,這里分為寫入特征、讀取特征、通知特征,分別是代表app想設(shè)備發(fā)送信息、app從設(shè)備獲取信息、app獲取設(shè)備的信息。
來到這里就已經(jīng)獲取到我們想要的特征了,接下來我們開始最后一步,寫入信息和讀取信息了。
下面的這個代理方法是,設(shè)備所有的數(shù)據(jù)都是從中這個代理方法返回的:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//從我們想要的特征中獲取返回的數(shù)據(jù)
if ([self.notifyCharteristic isEqual:characteristic]) {
NSLog(@"設(shè)備愛返回的數(shù)據(jù):%@",characteristic.value);
}
}
當(dāng)我們想向設(shè)備寫入數(shù)據(jù)的時候,調(diào)用下面這個方法:
if(self.characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse)
{
//手機(jī)向外設(shè)發(fā)送數(shù)據(jù),寫數(shù)據(jù)
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
注意:這里寫入的data是一個二進(jìn)制形式
寫到這里就結(jié)束了,上面就是講解了開頭的6個步驟的實現(xiàn)方法,利用原生的api去封裝一個藍(lán)牙通訊的SDK不難,關(guān)鍵是這個藍(lán)牙通訊的SDK結(jié)合自己的項目穿插各種邏輯的時候,就需要謹(jǐn)慎思考了。
謝謝~