上一次我們研究完iBeacon,發現iBeacon是基於藍牙4.0的一個封裝而已。那么,今天我們來研究iOS的藍牙4.0的應用。最出名的app當屬lightblue,我們不妨來仿寫一個lightblue,這樣基本的ios藍牙編程就算入門了。
基本理論
框架與概念
在ios中使用藍牙技術,會用到CoreBluetooth框架。
里面對設備有2個定義:周邊(peripeheral)設備 與 中央(central)設備。發送藍牙信號的是周邊設備,接收藍牙信號的是中央設備。
可以這樣理解,周邊設備是服務端,中央設備是客戶端。中央設備可以去搜索周邊有哪些服務端,可以選擇連接上其中一台,進行信息獲取。
支持藍牙4.0的手機,可以作為周邊設備,也可以作為中央設備,但是不能同時既為周邊設備又為中央設備。
類解讀
中央設備用 CBCentralManager
這個類管理。
周邊設備用 CBPeripheralManager
這個類管理;
周邊設備里面還有服務類 CBService
,服務里面有各種各樣的特性類 CBCharacteristic
。
仿寫lightblue
基本流程
- 假設我們有2台以上可用設備。
- 其中一台作為調試機,用來搜索其它設備,並連接上去。所以,是中央設備
central
。
- 其它設備設置為藍牙發射器,即是周邊設備
peripheral
。
- 調試機先掃描周邊設備,用UITableView展示所掃描到的周邊設備。
- 點擊其中一台設備,進行連接
connect
。
- 連接上后,獲取其中的所有服務
services
。
- 對其中每個服務進行遍歷,獲取所有的特性
Characteristic
。
- 讀取每個特性,獲取每個特性的值
value
。
至此,lightblue基本的仿寫思路就清晰列出來了。
1. 掃描設備
先包含頭文件
1
|
#import <CoreBluetooth/CoreBluetooth.h> |
然后添加協議 CBCentralManagerDelegate
接着定義2個屬性, CBCentralManager用來管理我們的中央設備,NSMutableArray用來保存掃描出來的周邊設備。
1
2 |
@property (nonatomic, strong) CBCentralManager *centralMgr; @property (nonatomic, strong) NSMutableArray *arrayBLE; |
中央設備創建很簡單,第一個參數代表 CBCentralManager
代理,第二個參數設置為nil,因為Peripheral Manager將Run在主線程中。如果你想用不同的線程做更加復雜的事情,你需要創建一個隊列(queue)並將它放在這兒。
1
2 |
self.centralMgr = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; self.arrayBLE = [[NSMutableArray alloc] init]; |
實現centralManagerDidUpdateState
。當Central Manager被初始化,我們要檢查它的狀態,以檢查運行這個App的設備是不是支持BLE。
1
2 3 4 5 6 7 8 9 10 11 12 13 |
- (void)centralManagerDidUpdateState:(CBCentralManager *)central { switch (central.state) { case CBCentralManagerStatePoweredOn: [self.centralMgr scanForPeripheralsWithServices:nil options:nil]; break; default: NSLog(@"Central Manager did change state"); break; } } |
-scanForPeripheralsWithServices:options:
方法是中央設備開始掃描,可以設置為特定UUID來指,來差找一個指定的服務了。我們需要掃描周邊所有設備,第一個參數設置為nil。
當發起掃描之后,我們需要實現 centralManager:didDiscoverPeripheral:advertisementData:RSSI:
通過該回調來獲取發現設備。
這個回調說明着廣播數據和信號質量(RSSI-Received Signal Strength Indicator)的周邊設備被發現。通過信號質量,可以用判斷周邊設備離中央設備的遠近。
1
2 3 4 5 6 7 8 9 |
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { BLEInfo *discoveredBLEInfo = [[BLEInfo alloc] init]; discoveredBLEInfo.discoveredPeripheral = peripheral; discoveredBLEInfo.rssi = RSSI; // update tableview [self saveBLE:discoveredBLEInfo]; } |
BLEInfo是我新建的一個類,用來存儲周邊設備信息的,具體如下:
1
2 3 4 5 6 |
@interface BLEInfo : NSObject @property (nonatomic, strong) CBPeripheral *discoveredPeripheral; @property (nonatomic, strong) NSNumber *rssi; @end |
保存周邊設備信息,並把它們顯示到UITableView上:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (BOOL)saveBLE:(BLEInfo *)discoveredBLEInfo { for (BLEInfo *info in self.arrayBLE) { if ([info.discoveredPeripheral.identifier.UUIDString isEqualToString:discoveredBLEInfo.discoveredPeripheral.identifier.UUIDString]) { return NO; } } [self.arrayBLE addObject:discoveredBLEInfo]; [self.tableView reloadData]; return YES; } |
掃描到的周邊設備展示如下:
掃描到的周邊設備
2. 連接設備
當我們點擊其中一個設備,嘗試進行連接。lightblue是點擊后就立馬連接的,然后在下一個UITableView來展示該周邊設備的服務與特性。
而我是進入下一頁UITableView才開始連接,差別不大。但是注意的是,一定要把我們之前的self.centralMgr傳遞到下一頁的UITableView來使用,並且重新設置delegate。
用來展示服務和特性的UITableViewController:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#import <UIKit/UIKit.h> #import <CoreBluetooth/CoreBluetooth.h> @interface BLEInfoTableViewController : UITableViewController < CBPeripheralManagerDelegate, CBCentralManagerDelegate, CBPeripheralDelegate > @property (nonatomic, strong) CBCentralManager *centralMgr; @property (nonatomic, strong) CBPeripheral *discoveredPeripheral; // tableview sections,保存藍牙設備里面的services字典,字典第一個為service,剩下是特性與值 @property (nonatomic, strong) NSMutableArray *arrayServices; // 用來記錄有多少特性,當全部特性保存完畢,刷新列表 @property (atomic, assign) int characteristicNum; @end |
記得把之前的centrlMgr傳過來,記得要重新設置delegate:
1
2 3 4 5 6 7 8 9 10 11 12 |
- (void)viewDidLoad { [super viewDidLoad]; [_centralMgr setDelegate:self]; if (_discoveredPeripheral) { [_centralMgr connectPeripheral:_discoveredPeripheral options:nil]; } _arrayServices = [[NSMutableArray alloc] init]; _characteristicNum = 0; } |
其中,
[centralMgr connectPeripheral:discoveredPeripheral options:nil];
就是中央設備向周邊設備發起連接。
我們可以實現下面的函數,如果連接失敗,就會得到回調:
1
2 3 4 |
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"didFailToConnectPeripheral : %@", error.localizedDescription); } |
我們必須實現didConnectPeripheral
,只要連接成功,就能回調到該函數,開始獲取服務。
1
2 3 4 5 6 7 8 9 |
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { [self.arrayServices removeAllObjects]; [_discoveredPeripheral setDelegate:self]; [_discoveredPeripheral discoverServices:nil]; } |
discoverServices
就是查找該周邊設備的服務。
3. 獲取服務
當找到了服務之后,就能進入didDiscoverServices
的回調。我們把全部服務都保存起來。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { if (error) { NSLog(@"didDiscoverServices : %@", [error localizedDescription]); // [self cleanup]; return; } for (CBService *s in peripheral.services) { NSLog(@"Service found with UUID : %@", s.UUID); NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithDictionary:@{SECTION_NAME:s.UUID.description}]; [self.arrayServices addObject:dic]; [s.peripheral discoverCharacteristics:nil forService:s]; } } |
4. 獲取特性
我們通過discoverCharacteristics
來獲取每個服務下的特性,通過下面的回調來獲取。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { if (error) { NSLog(@"didDiscoverCharacteristicsForService error : %@", [error localizedDescription]); return; } for (CBCharacteristic *c in service.characteristics) { self.characteristicNum++; [peripheral readValueForCharacteristic:c]; } } |
5. 獲取特性值
readValueForCharacteristic
可以讀取特性的值。
通過下面的回調,就能得到特性值。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { self.characteristicNum--; if (self.characteristicNum == 0) { [self.tableView reloadData]; } if (error) { NSLog(@"didUpdateValueForCharacteristic error : %@", error.localizedDescription); return; } NSString *stringFromData = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]; if ([stringFromData isEqualToString:@"EOM"]) { NSLog(@"the characteristic text is END"); // [peripheral setNotifyValue:NO forCharacteristic:characteristic]; // [self.centralMgr cancelPeripheralConnection:peripheral]; } for (NSMutableDictionary *dic in self.arrayServices) { NSString *service = [dic valueForKey:SECTION_NAME]; if ([service isEqual:characteristic.service.UUID.description]) { NSLog(@"characteristic.description : %@", characteristic.UUID.description); [dic setValue:characteristic.value forKey:characteristic.UUID.description]; } } } |
連接到周邊設備獲得的藍牙信息
其它
本來蘋果是提供了xcode5.0加ios7的模擬器來實現模擬器開啟藍牙的,本來連文章都給出了:https://developer.apple.com/library/ios/technotes/tn2295/_index.html
后來蘋果把這文章給刪了,還把ios7模擬器支持開啟藍牙給去掉。
那么,可以通過這個文章http://blog.csdn.net/zhenyu5211314/article/details/24399887,使用6.0的模擬器來調試。
參考文章
iOS CoreBluetooth 教程
藍牙 BLE CoreBluetooth 初探
藍牙4.0 For IOS