個(gè)人認(rèn)為iOS藍(lán)牙開發(fā)算是相對(duì)偏門的領(lǐng)域,做iOS藍(lán)牙相關(guān)的開發(fā)也有一段時(shí)間了,在此寫下藍(lán)牙相關(guān)的東西,一則方便日后查閱,二則給后來之人一點(diǎn)參考。
關(guān)于藍(lán)牙是什么就不多做解釋了,百度一搜一大把。iOS藍(lán)牙開發(fā)。藍(lán)牙開發(fā)有兩種:
1.手機(jī)做中心 - CBCentralManager、外部硬件做外設(shè) - CBPeripheral
2.外部硬件做中心 - CBCentralManager、手機(jī)做外設(shè) - CBPeripheral
一般情況下開發(fā),都是以手機(jī)(App)作為中心,硬件設(shè)備(智能硬件、手環(huán)之類的)作為外設(shè)。這里只記錄這種情況。
最開始主要有兩個(gè)概念:服務(wù)(CBService)、特征(CBCharacteristic)。
服務(wù):每個(gè)設(shè)備都會(huì)有一些服務(wù),每個(gè)服務(wù)里面都會(huì)有一些特征,特征就是具體鍵值對(duì),提供數(shù)據(jù)的地方。每個(gè)特征屬性分為這么幾種:讀,寫,通知這么幾種方式。
特征:讀、寫數(shù)據(jù)等。
畫了個(gè)簡(jiǎn)圖,沒有美術(shù)天賦。(“、、、”表示重復(fù)的省略)

剛接觸藍(lán)牙的人,估計(jì)會(huì)不太理解服務(wù)和特性是什么東西,我剛剛開始也沒搞明白。我最開始接觸藍(lán)牙是從大學(xué)玩單片機(jī)的藍(lán)牙2.0開始的(蘋果是4.0),學(xué)電子的應(yīng)該知道。那時(shí)候只需要設(shè)置AT指令,調(diào)用收發(fā)數(shù)據(jù)的API即可。
我的理解是:
【1】服務(wù)是用來提供傳輸數(shù)據(jù)通道的。比如,一個(gè)人體數(shù)據(jù)監(jiān)測(cè)儀,服務(wù)A用于傳輸心率、服務(wù)B用于傳輸血壓、服務(wù)C用于傳輸腦電波。。。嗯,是的,這么打個(gè)比方吧。
【2】服務(wù)A中(傳輸心率那個(gè))有:特性1->用于App讀取數(shù)據(jù)、特性2->用于App發(fā)送指令或者數(shù)據(jù)到硬件,特性3->用于讀取硬件的mac地址。這樣就將這些功能模塊(心率、血壓、腦電波等)及其接口(讀取、發(fā)送等)都很好的分開了,我理解成面向?qū)ο蟮慕怦睢?br>
下面是已手機(jī)App作為中心的大致步驟:
-[1]創(chuàng)建管理中心,并設(shè)置其代理。
-[2]判斷手機(jī)藍(lán)牙狀態(tài)。(是否打開、是否支持、是否授權(quán)),這里可以做提示用戶打開手機(jī)藍(lán)牙之類的。。。
-[3]掃描外設(shè)。每次掃描到一個(gè)藍(lán)牙外設(shè),都會(huì)通過管理中心的代理方法將外設(shè)相關(guān)信息回調(diào)過來(外設(shè)名字、信號(hào)值、廣播等),此時(shí)可以自定義一個(gè)對(duì)象用于保存這個(gè)外設(shè)。然后將這個(gè)模型添加進(jìn)數(shù)組中,利用自定義tableview顯示出來,就和網(wǎng)絡(luò)請(qǐng)求展示數(shù)據(jù)類似。
-[4]連接外設(shè)。
-[5]連接外設(shè)成功,尋找需要的服務(wù)(比如我只需要監(jiān)測(cè)心率,不需要發(fā)腦電波,那就只需要找到監(jiān)測(cè)心率那個(gè)服務(wù)就好了)。
-[6]尋找到需要的服務(wù)后,接下來 -> 尋找需要的特性(讀數(shù)據(jù)、寫指令、讀mac等需要的特性)。找到的這些特性,都將其作為屬性保存起來,后續(xù)發(fā)送數(shù)據(jù)等會(huì)需要用到。
-[7]訂閱讀數(shù)據(jù)的特性。這個(gè)特性是這樣的:每次硬件傳輸數(shù)據(jù)過來時(shí),都會(huì)走這里,所以需要訂閱它,以便能收到硬件的數(shù)據(jù)。
-[8]發(fā)送自己的指令,注意:每次最多只能發(fā)送20個(gè)字節(jié)。
-[9]有以上步驟,收發(fā)數(shù)據(jù)基本OK了,還有的就是寫完善功能:自動(dòng)連接,斷線重連,過濾不需要的設(shè)備,按指定mac地址連接等等。
以下是相關(guān)代碼:
///創(chuàng)建管理中心
_manager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil];///默認(rèn)在主線程中
///初始化數(shù)組,用于存放搜索到的外設(shè)
_allPeripheral = [NSMutableArray array];
///開始掃描外設(shè)
[_manager scanForPeripheralsWithServices:nil options:nil];
以下是管理中心代理方法:
#pragma mark - - - - - -管理中心代理 - - - - - - - - -
#pragma mark 【1】監(jiān)測(cè)藍(lán)牙狀態(tài)
///這里可以做相應(yīng)處理,當(dāng)藍(lán)牙狀態(tài): 未打開->提示用于打開藍(lán)牙
/// 其他->提示其他
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state){
case CBManagerStateUnknown:
NSLog(@"藍(lán)牙-未知");
break;
case CBManagerStateUnsupported:
NSLog(@"藍(lán)牙-不支持");
break;
case CBManagerStateUnauthorized:
NSLog(@"藍(lán)牙-未授權(quán)");
break;
case CBManagerStatePoweredOff:{///藍(lán)牙關(guān)閉
NSLog(@"藍(lán)牙-已關(guān)閉");
}
break;
case CBManagerStateResetting:
NSLog(@"藍(lán)牙-復(fù)位");
break;
case CBManagerStatePoweredOn:{///藍(lán)牙打開
NSLog(@"藍(lán)牙-已打開");
}
break;
}
}
#pragma mark 【2】發(fā)現(xiàn)外部設(shè)備
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI{
BLEModel *model = [[BLEModel alloc] initWithPeripheral:peripheral rssi:RSSI advertisementData:advertisementData];
for (BLEModel *saveModel in _allPeripheral) {
if ([saveModel.peripheral isEqual:peripheral]) {
return;
}
}
[_allPeripheral addObject:model];
[_tableView reloadData];
}
#pragma mark 【3】連接外部藍(lán)牙設(shè)備
- (void)connectToPeripheral:(CBPeripheral *)peripheral{
if (!peripheral) {
return;
}
[_manager connectPeripheral:peripheral options:nil];
}
#pragma mark 【4】連接外部藍(lán)牙設(shè)備成功
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral{
///連接成功
NSLog(@"連接成功,開始尋找服務(wù)和特征");
[peripheral discoverServices:nil];
}
#pragma mark 【5】連接外部藍(lán)牙設(shè)備失敗
- (void)centralManager:(CBCentralManager *)central
didFailToConnectPeripheral:(CBPeripheral *)peripheral
error:(NSError *)error{
///這里可以嘗試重連
[_manager connectPeripheral:peripheral options:nil];
}
#pragma mark 【6】藍(lán)牙外設(shè)連接斷開,自動(dòng)重連
- (void)centralManager:(CBCentralManager *)central
didDisconnectPeripheral:(CBPeripheral *)peripheral
error:(NSError *)error{
///當(dāng)連接斷開時(shí),會(huì)走這個(gè)回調(diào),可以做重連等
}
以下是外設(shè)代理方法:
#pragma mark - - - - - -外設(shè)代理 - - - - - - - - -
#pragma mark 【1】尋找藍(lán)牙服務(wù)
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if(error){
NSLog(@"外圍設(shè)備尋找服務(wù)過程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
}
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
for (CBService *service in peripheral.services) {
if([service.UUID isEqual:serviceUUID]){
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kNotifyUUID],[CBUUID UUIDWithString:kWriteUUID],[CBUUID UUIDWithString:kReadMacUUID]] forService:service];
}
}
}
#pragma mark 【2】尋找藍(lán)牙服務(wù)中的特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if (error) {//報(bào)錯(cuò)直接返回退出
NSLog(@"didDiscoverCharacteristicsForService error : %@", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics)
{
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kNotifyUUID]]){///訂閱讀數(shù)據(jù),執(zhí)行此行代碼,每次收到數(shù)據(jù)時(shí)才會(huì)走下面 “【8】直接讀取特征值被更新后”
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
self.readCharacteristic = characteristic;
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kWriteUUID]]) {
///保存寫數(shù)據(jù)的特征,用于給硬件設(shè)備發(fā)送數(shù)據(jù)
self.writeCharacteristic = characteristic;
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kReadMacUUID]]) {
///這個(gè)特征,在我目前的項(xiàng)目中是讀取藍(lán)牙m(xù)ac地址的
self.readMACCharacteristic = characteristic;
}
}
if (_readCharacteristic && _writeCharacteristic) {
NSLog(@"連接成功");///此時(shí)才算真正連接成功,因?yàn)榇藭r(shí)才有讀、寫特征,可以正常進(jìn)行數(shù)據(jù)交互
}
}
#pragma mark 【3】直接讀取特征值被更新后、即收到訂閱的那個(gè)特征的數(shù)據(jù)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"更新特征值時(shí)發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
return;
}
NSLog(@"收到數(shù)據(jù) -- %@",characteristic.value);
///這里就是處理數(shù)據(jù)了
}
然后是tableView代理
#pragma mark - tableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _allPeripheral.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
BLETableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BLETableViewCell" forIndexPath:indexPath];
cell.model = _allPeripheral[indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
BLEModel *bleModel = _allPeripheral[indexPath.row];
bleModel.peripheral.delegate = self;
[_manager connectPeripheral:bleModel.peripheral options:nil];
}
代碼中的幾個(gè)宏定義:
#pragma mark - 藍(lán)牙服務(wù)及屬性
#define kServiceUUID @"0001"
#define kWriteUUID @"0002"
#define kNotifyUUID @"0003"
#define kReadMacUUID @"0004"
為什么是0001、0002等,是有原因的,這里介紹個(gè)App,叫LightBlue,可以查看到藍(lán)牙設(shè)備的特性。當(dāng)然這些也可以找硬件工程師拿到,或者自己查DataSheet也可以查到。
再介紹個(gè)串口調(diào)試助手,CoolTerm。能實(shí)時(shí)調(diào)試藍(lán)牙模塊。
代碼地址:https://github.com/chan106/BLEDemo.git