由于最近工作的東家是一家物聯網公司,網上BLE相關的資料確實比較少,尤其我還做了一些調試和加密相關的工作.
由于調試和獲取數據的一些問題,自己一開始做開發的時候也不是那么從容.所以決定總結一下我在工作中遇到的問題和情況,希望這些東西能對其他做BLE相關的親們有點幫助,其實也是對自己的情況的不斷總結
一: 文件的初始化和配置
-
創建一個控制器,我這里就取名稱為CJCenterManagerVC,并包含頭文件
#import <CoreBluetooth/CoreBluetooth.h>
-
遵守協議:
@interface CJCenterManagerVC() <CBCentralManagerDelegate, CBPeripheralDelegate>
3.如果在開發中用到數據庫的話還要配置數據庫,libsqlite3.tdb框架,具體位置如下:
二: 藍牙BLE4.0具體思路及其講解
- 1、BLE4.0 分為外設和中心管理者兩個部分,通常說外設就是外部的連接設備,而中心管理者通常就是指手機,所以要聲明這個兩個屬性,并且在合適的地方初始化中心管理者,我這里就寫在了ViewDidLoad方法中,實際開發中可根據實際情況在合適的方法中初始化
聲明屬性:
@interface CJCenterManagerVC () <CBCentralManagerDelegate, CBPeripheralDelegate>
/** 中心管理者 */
@property (nonatomic, strong) CBCentralManager *cMgr;
/** 連接到的外設 */
@property (nonatomic, strong) CBPeripheral *peripheral;
@property (nonatomic, assign) sqlite3 *db;
@end
初始化:
- (void)viewDidLoad
{
[self cMgr];
}
- 2、當中心管理者初始化就會調用的方法:
// 只要中心管理者初始化,就會觸發此代理方法, 在這里判斷中心管理者的狀態是否開啟,如果開啟,就搜索外設
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@"CBCentralManagerStateUnknown");
break;
case CBCentralManagerStateResetting:
NSLog(@"CBCentralManagerStateResetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@"CBCentralManagerStateUnsupported");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@"CBCentralManagerStateUnauthorized");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@"CBCentralManagerStatePoweredOff");
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@"CBCentralManagerStatePoweredOn");
// 在中心管理者成功開啟后再進行一些操作
// 搜索外設
[self.cMgr scanForPeripheralsWithServices:nil // 通過某些服務篩選外設
options:nil]; // dict,條
}
break;
default:
break;
}
}
-
3、當發現外設后代理會自動調用下面的方法,我這里通過判斷外設的名稱來連接外設,具體屬性含義,看代碼的注釋
// 發現外設后調用的方法 - (void)centralManager:(CBCentralManager *)central // 中心管理者 didDiscoverPeripheral:(CBPeripheral *)peripheral // 外設 advertisementData:(NSDictionary *)advertisementData // 外設攜帶的數據 RSSI:(NSNumber *)RSSI // 外設發出的藍牙信號強度 { // 在此處對我們的 advertisementData(外設攜帶的廣播數據) 進行一些處理 if ([peripheral.name isEqualToString:@"iTAG"] || [peripheral.name isEqualToString:@"ITAG"]) { // 標記我們的外設,讓他的生命周期 = vc self.peripheral = peripheral; // 發現完之后就是進行連接 [self.cMgr connectPeripheral:self.peripheral options:nil]; } }
-
4、此時就會進行外設和手機之間的連接,這期間的連接由硬件完成,我們只需在連接完成后的回調里做事情
// 中心管理者連接外設成功 - (void)centralManager:(CBCentralManager *)central // 中心管理者 didConnectPeripheral:(CBPeripheral *)peripheral // 外設 { // 斷開連接,讓管理者不再繼續搜索設備 [self ccj_dismissConentedWithPeripheral:peripheral IsCancle:false]; // 設置外設的代理 self.peripheral.delegate = self; // 外設發現服務,傳nil代表不過濾 [self.peripheral discoverServices:nil]; // 讀取RSSI的值 [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(readRSSIInfo) userInfo:nil repeats:YES]; }
4.1 斷開連接的代碼和讀RSSI的代碼
// 斷開連接
- (void)ccj_dismissConentedWithPeripheral:(CBPeripheral
*)peripheral IsCancle:(BOOL)cancle
{
// 停止掃描
[self.cMgr stopScan];
if (cancle) {
// 斷開連接
[self.cMgr cancelPeripheralConnection:peripheral];
}
}
// 讀RSSI值
- (void)readRSSIInfo
{
[self.peripheral readRSSI];
}
4.2 注意點: readRSSIInfo的代碼的含義是為了讓設備發送讀取指令,這樣設備才能繼續調用獲取到RSSI值的回調方法
-
5、如果外設沒有連接上會調用如下方法
// 外設連接失敗 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"%s, line = %d, %@=連接失敗", __FUNCTION__, __LINE__, peripheral.name); } // 丟失連接 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"%s, line = %d, %@=斷開連接", __FUNCTION__, __LINE__, peripheral.name); }
-
6、當發現外設的服務后會調用的方法
// 發現外設的服務后調用的方法 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { // 判斷沒有失敗 if (error) { NSLog(@"%s, line = %d, error = %@", __FUNCTION__, __LINE__, error.localizedDescription); return; #warning 下面的方法中凡是有error的在實際開發中,都要進行判斷 } for (CBService *service in peripheral.services) { // 發現服務后,讓設備再發現服務內部的特征們 [peripheral discoverCharacteristics:nil forService:service]; } }
-
7、發現外設的服務特征的時候調用的方法, 一般在這里我們會獲取某些比如電量的值或者進行加密通道的寫入數據
// 發現外設服務里的特征的時候調用的代理方法 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { if (error) { NSLog(@"%s, line = %d, %@", __FUNCTION__, __LINE__, [error description]); return; } for (CBCharacteristic *chara in service.characteristics) { // 外設讀取特征的值 // 電量特征和服務 (BatService和BatChara為自定義define的電量服務) if([chara.UUID isEqual:[CBUUID UUIDWithString:BatChara]] && [service.UUID isEqual:[CBUUID UUIDWithString:BatService]]) { // 此時的chara 就是電量的特征,在這里通過chara.value獲取值進而轉化為電量值 } } }
7.1 其他獲取值的方法同上類似,獲取電量值的具體方法請自己谷歌,如果找不到的話也可以給我留言,我看到會回復
注: 更新于: 2016年11月29日, 藍牙開發者門戶網站已改版,共用的協議接口漢語版本已找不到了, 下面更新的協議接口地址為英文版, 如果哪位小伙伴有漢語版也歡迎留言
已采納的公用的協議接口請參考: Bluetooth開發者門戶
-
8、只要特征和描述的value一更新就會調用的方法
// 更新特征的value的時候會調用 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { for (CBDescriptor *descriptor in characteristic.descriptors) { // 它會觸發 [peripheral readValueForDescriptor:descriptor]; } if (error) { return; } } // 更新特征的描述的值的時候會調用 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error { // 這里當描述的值更新的時候,直接調用此方法即可 [peripheral readValueForDescriptor:descriptor]; }
發現外設的特征的描述數組:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
{
// 在此處讀取描述即可
for (CBDescriptor *descriptor in characteristic.descriptors) {
// 它會觸發
[peripheral readValueForDescriptor:descriptor];
}
-
9、接收到通知調用的方法
//接收到通知 -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { NSLog(@"%@", [error localizedDescription]); return; } if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:NotCharaOrDes]]) { return; } [peripheral readValueForCharacteristic:characteristic];//接受通知后讀取 [peripheral discoverDescriptorsForCharacteristic:characteristic]; }
10、讀取到信號回調,在這里我們可以拿到信號RSSI的值,這里也就是readRSSIInfo方法回調的地方.
備注: 在安卓端,只要控制了readRSSIInfo的方法調用頻率, 下面的回調方法可以按照頻率來調用,比如說0.1秒,但是我親自試過, 在我們iOS端,如果你控制了readRSSIInfo的調用頻率,這個頻率大于1秒鐘,那么就會按照你所控制的頻率進行下面的方法回調,但是如果小于1秒鐘,都是默認為1秒鐘調用下面的回調方法一次! 所以也就是說RSSI的值只能最快1秒鐘獲取一次
//讀取到信號回調
-(void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error
{
}
-
11、收到反饋時回調
// 收到反饋時調用 - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error { [peripheral readValueForCharacteristic:characteristic]; }
-
12、自定義方法
// 外設寫數據到特征中 // 需要注意的是特征的屬性是否支持寫數據 - (void)ccj_peripheral:(CBPeripheral *)peripheral didWriteData:(NSData *)data forCharacteristic:(nonnull CBCharacteristic *)characteristic { if (characteristic.properties & CBCharacteristicPropertyWrite) { // 核心代碼在這里 [peripheral writeValue:data // 寫入的數據 forCharacteristic:characteristic // 寫給哪個特征 type:CBCharacteristicWriteWithResponse];// 通過此響應記錄是否成功寫入 } } // 通知的訂閱和取消訂閱 // 實際核心代碼是一個方法 // 一般這兩個方法要根據產品需求來確定寫在何處 - (void)ccj_peripheral:(CBPeripheral *)peripheral regNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic { // 外設為特征訂閱通知 數據會進入peripheral:didUpdateValueForCharacteristic:error:方法 [peripheral setNotifyValue:YES forCharacteristic:characteristic]; } - (void)ccj_peripheral:(CBPeripheral *)peripheral CancleRegNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic { // 外設取消訂閱通知 數據會進入 peripheral:didUpdateValueForCharacteristic:error:方法 [peripheral setNotifyValue:NO forCharacteristic:characteristic]; }
// 斷開連接
- (void)ccj_dismissConentedWithPeripheral:(CBPeripheral *)peripheral IsCancle:(BOOL)cancle
{
// 停止掃描
[self.cMgr stopScan];
if (cancle)
{
// 斷開連接
[self.cMgr cancelPeripheralConnection:peripheral];
}
}
三: 其他
關于藍牙的加密通道的具體實現 和測試的具體實現會寫在其他的篇幅中,如果發現本博客哪處有問題或者您有任何問題,歡迎留言指正和探討,讓我們共同進步!