一、藍牙基礎(chǔ)知識
(一)常見簡稱
MFI make for ipad ,iphone, itouch 專們?yōu)樘O果設(shè)備制作的設(shè)備,開發(fā)使用ExternalAccessory 框架(認證流程貌似挺復(fù)雜的,而且對公司的資質(zhì)要求較高),詳見:關(guān)于MFi認證你所必須要知道的事情
BLE buletouch low energy介褥,藍牙4.0設(shè)備因為低耗電夏伊,所以也叫做BLE,開發(fā)使用CoreBluetooth 框架
(二)兩種模式
- CBCentralMannager 中心模式 :以手機(app)作為中心房蝉,連接其他外設(shè)的場景(主要寫此種該模式的應(yīng)用方法公荧,因為我只會這種模式的)
- CBPeripheralManager 外設(shè)模式:使用手機作為外設(shè)連接其他中心設(shè)備操作的場景
(三)CBPeripheral 带射、CBService、CBCharacteristic
三者關(guān)系:
一個CBPeripheral有一個或者多個CBService循狰,而每一個CBService有一個或者多個CBCharacteristic窟社,通過可寫的CBCharacteristic發(fā)送數(shù)據(jù),而每一個CBCharacteristic有一個或者多個Description用于描述characteristic的信息或?qū)傩?/p>
(四)關(guān)于藍牙設(shè)備唯一表示的問題
在藍牙開發(fā)中券勺,iOS 沒有直接提供獲取mac 地址的方法(Android 是可以直接獲取的),所以在iOS藍牙開發(fā)中就有唯一標識的問題了
當我們使用CoreBluetooth系統(tǒng)框架進行藍牙開發(fā)的時候灿里,有時候某種功能需要和指定的藍牙設(shè)備進行操作关炼,這就需要我們拿到藍牙設(shè)備的唯一標識,來確定是哪一臺設(shè)備匣吊,先看下一當我們掃描到的藍牙設(shè)備時儒拂,所能拿到的屬性:
針對于不同的業(yè)務(wù)需求,我們在進行連接操作的時候色鸳,不要指定具體那一臺設(shè)備的社痛,那么就可以使用identifier來作為唯一標識
** 這里有一個坑要注意: 對于同一臺藍牙設(shè)備,不同手機進行掃描命雀,然后讀取的 identifier是不同的**
而對于需要指定的到藍牙設(shè)備的解決辦法:
- 將mac 地址放在藍牙設(shè)備的廣播數(shù)據(jù)當中蒜哀,然后在廣播的時候,將mac地址一廣播的形式發(fā)出來
- 將 mac 地址寫在某一個藍牙特征通道中吏砂,當我們連接藍牙設(shè)備之后凡怎,通過某一個特征通道獲取mac地址
- 我們可以通過藍牙設(shè)備出廠設(shè)備 或者 后期手動修改藍牙設(shè)備的name,作為唯一標識
二赊抖、CoreBluetooth常用到的方法
自己封裝了一個藍牙單例的類,對藍牙開發(fā)中常用到的方法寫了較為詳細的解釋寨典,代碼如下:
+ (instancetype)shared {
static SLBluetoothCentralManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
/**
初始化 centralManager
*/
- (instancetype)init {
self = [super init];
if (self) {
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
return self;
}
/**
掃描周周的設(shè)備
*/
- (void) sacnNearPerpherals {
TCLog(@"開始掃描四周的設(shè)備");
/**
1.第一個參數(shù)為Services的UUID(外設(shè)端的UUID) 不能為nil
2.第二參數(shù)的CBCentralManagerScanOptionAllowDuplicatesKey為已發(fā)現(xiàn)的設(shè)備是否重復(fù)掃描氛雪,如果是同一設(shè)備會多次回調(diào)
*/
[self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @NO}];
}
#pragma mark - CBCentralManagerDelegate
/**
檢查App設(shè)備藍牙是否可用
@param central 中心設(shè)備管理器
*/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
switch (central.state){
case CBCentralManagerStatePoweredOn://藍牙已打開,開始掃描外設(shè)
[self sacnNearPerpherals];
break;
case CBCentralManagerStateUnsupported:
[MBProgressHUD showMessage:@"您的設(shè)備不支持藍牙或藍牙4.0" toView:nil];
break;
case CBCentralManagerStateUnauthorized:
[MBProgressHUD showMessage:@"未授權(quán)打開藍牙" toView:nil];
break;
case CBCentralManagerStatePoweredOff://藍牙未打開,系統(tǒng)會自動提示打開耸成,所以不用自行提示
default:
break;
}
}
/**
發(fā)現(xiàn)外圍設(shè)備的代理
@param central 中心設(shè)備
@param peripheral 外圍設(shè)備
@param advertisementData 特征數(shù)據(jù)
@param RSSI 信號強度
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
NSMutableString* nsmstring=[NSMutableString stringWithString:@"\n"];
[nsmstring appendString:@"----發(fā)現(xiàn)外設(shè)----\n"];
[nsmstring appendString:@"Peripheral Info:\n"];
[nsmstring appendFormat:@"NAME: %@\n",peripheral.name];
[nsmstring appendFormat:@"UUID(identifier): %@\n",peripheral.identifier];
[nsmstring appendFormat:@"RSSI: %@\n",RSSI];
[nsmstring appendFormat:@"adverisement:%@\n",advertisementData];
TCLog(@"%@",nsmstring);
NSArray *serviceUUIDArr = advertisementData[@"kCBAdvDataServiceUUIDs"];
for (CBUUID *serviceUUID in serviceUUIDArr) {
// 判斷外設(shè)是否有需要的服務(wù)(是否是當前APP對應(yīng)的外設(shè))<此項目應(yīng)該判斷外設(shè)的名字>
if ([serviceUUID.UUIDString isEqualToString:kServiceUUID]) {
//發(fā)現(xiàn)符合條件的周邊外設(shè)通知
[[NSNotificationCenter defaultCenter] postNotificationName: SLDidFoundPeripheralNotification object:peripheral];
[self connectPeripheral:peripheral];
}
}
}
/// 連接指定的設(shè)備
- (void)connectPeripheral:(CBPeripheral *)peripheral {
//這個引用不可以省略
self.peripheral = peripheral;
TCLog(@"----嘗試連接設(shè)備----\n%@", peripheral);
[self.centralManager connectPeripheral:peripheral
options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
}
/// 連接外設(shè)成功的代理方法
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
TCLog(@"%@連接成功",peripheral.name);
// 設(shè)置設(shè)備代理
[self.peripheral setDelegate:self];
[self.peripheral discoverServices:nil];
}
///連接外設(shè)失敗的代理方法
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
TCLog(@"%@連接失敗",peripheral.name);
}
///連接外設(shè)中斷的代理方法
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
TCLog(@"%@連接已斷開",peripheral.name);
}
#pragma mark - CBPeripheralDelegate
/// 獲取外設(shè)服務(wù)的代理
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
if (error) {
TCLog(@"%@獲取服務(wù)失敗:%@",peripheral.name,error.localizedDescription);
return;
}
for (CBService *service in peripheral.services) {
// 找到對應(yīng)服務(wù)
if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
//服務(wù)中找特征
[service.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kCharacteristicUUID]] forService:service];
}
}
}
///在獲取外設(shè)服務(wù)的代理的方法中如果沒有error报亩,可以調(diào)用discoverCharacteristics方法請求周邊去尋找它的服務(wù)所列出的特征,它會響應(yīng)下面的方法
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if (error) {
TCLog(@"%@獲取指定特征失敗:%@",peripheral.name,error.localizedDescription);
return;
}
NSMutableString* nsmstring=[NSMutableString stringWithString:@"\n"];
[nsmstring appendFormat:@"------在服務(wù): %@ 中發(fā)現(xiàn) %lu 個特征 ------\n",service.UUID,(unsigned long)service.characteristics.count];
for (CBCharacteristic *characteristic in service.characteristics) {
[nsmstring appendFormat:@"%@\n",characteristic];
[nsmstring appendFormat:@"\n"];
TCLog(@"%@",nsmstring);
self.peripheral = peripheral;
self.writeCharacteristic = characteristic;
// 連接成功井氢,開始配對 - 發(fā)送第一次校驗的數(shù)據(jù)
[self willPairToPeripheral:peripheral];
}
}
///已連接上設(shè)備,開始進行配對
- (void)willPairToPeripheral:(CBPeripheral *)peripheral{
//發(fā)送第一次校驗的數(shù)據(jù)
NSData *firstAuthData = [SLCheckoutDataUtils firstRandomData];
[[SLBluetoothCentralManager shared] sendCommandData:firstAuthData];
}
/// 寫入數(shù)據(jù)方法
- (void)sendCommandData:(NSData *)data {
if(self.writeCharacteristic.properties & CBCharacteristicPropertyWrite || self.writeCharacteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) {
[self.peripheral writeValue:data forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];
TCLog(@"----寫入命令---->:cmd:%@\n\n", data);
} else {
TCLog(@"該字段不可寫弦追!");
}
}
/// 寫入數(shù)據(jù)后的回調(diào)方法
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"寫入失敗: %@",[error localizedDescription]);
return;
}
NSLog(@"寫入成功-->%@",characteristic.value);
[peripheral readValueForCharacteristic:characteristic];
}
/// 獲取到特征的值時回調(diào) -- 獲取回調(diào)數(shù)據(jù)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
TCLog(@"特征UUID:%@回調(diào)數(shù)據(jù)錯誤:%@", characteristic.UUID.UUIDString,error.localizedDescription);
return;
}
TCLog(@"------特征收到回調(diào)數(shù)據(jù)------> uuid:%@ \nvalue: %@\n\n",characteristic.UUID,characteristic.value);
NSString *result = [NSString jkt_hexStringFromData:characteristic.value];
if ([result hasPrefix:SLDidFirstPairSuccessCommand]) {//第一次配對成功
NSData *secondAuthData = [SLCheckoutDataUtils secondVerifyData:characteristic.value];
[[SLBluetoothCentralManager shared] sendCommandData:secondAuthData];
}
if ([result hasPrefix:SLDidSecondPairSuccessCommand]) {//第二次配對成功
TCLog(@"正式建立的連接-----------");
//開鎖測試
[self performSelector:@selector(sendOpenLockCmd) withObject:nil afterDelay:3.0];
}
}
對于常用的方法,都寫了注釋花竞,希望對于第一藍牙開發(fā)的小伙伴有幫助>⒓!
三约急、BabyBluetooth
推薦這個庫,很??零远, 對CoreBluetooth進行了封裝,如果你不喜歡系統(tǒng)的各種代理方法厌蔽,那么可以試一試這個庫牵辣,也許你會喜歡
如果有些的不對的,不吝指教EN诚颉择浊!