藍牙知識簡介
藍牙硬件
1、藍牙4.0:包括藍牙4.0以上的版本税肪,iOS 6 以上才可以使用御板,不一定需要MFI認證锥忿,因為4.0以上是低功耗,現(xiàn)在大部分的藍牙設備都是4.0以上怠肋,本文主要講藍牙4.0及以上設備開發(fā)敬鬓,使用CoreBluetooth 框架。
2笙各、藍牙2.0:又稱經(jīng)典藍牙钉答,2.0的藍牙硬件模塊必須經(jīng)過蘋果的MFI認證,否則無法進行藍牙開發(fā)杈抢,使用ExternalAccessory 框架数尿。
藍牙基礎知識
MFI ---- 是apple公司 “Made for iOS”的英文縮寫,是蘋果公司對其授權配件廠商生產(chǎn)的外置配件的一種標識使用許可惶楼。有MFI標識的設備質(zhì)量普遍較好右蹦。
BLE ---- buletouch low energy,藍牙4.0以上設備因為低耗電歼捐,所以也叫做BLE何陆。
central,peripheral ---- 中心豹储,外設,發(fā)起連接的一端叫central贷盲,被連接的設備為perilheral。
service剥扣,Characteristic巩剖,Description ---- 服務,特征钠怯,描述佳魔。都可以從設備讀取到。一個設備可以有多個服務晦炊,一個服務可以有多個特征鞠鲜,特征的權限分為寫write,讀read刽锤,通知notify等幾種镊尺,每個服務都有相應的描述朦佩〔⑺迹可類比為:服務wervice=類Class,特征Characteristic=屬性property语稠,Description描述=注釋宋彼。
CoreBluetooth框架主要包含兩種業(yè)務模式:中心模式和外設模式弄砍,前者是用手機連接其他藍牙設備,后者是手機當做藍牙設備被連接输涕。本人最近只用到中心模式音婶。
連接藍牙流程:
1、創(chuàng)建中心對象
2莱坎、掃描周圍藍牙設備
3衣式、連接藍牙設備
4、掃描藍牙設備的服務和特征
5檐什、和藍牙設備的數(shù)據(jù)交互
6碴卧、斷開連接
中心模式的應用場景:例如用手機上的APP去連接藍牙設備(手環(huán)等)。藍牙設備的服務和特征一般有硬件工程師定義好乃正,以及每個特征的屬性(讀住册,寫,通知)瓮具,類似于后臺定義接口荧飞,APP端調(diào)用。讀屬性用于從藍牙設備獲取數(shù)據(jù)名党,寫屬性用于往藍牙設備傳數(shù)據(jù)叹阔,通知是訂閱一個屬性后,一旦屬性的值發(fā)生改變就會通知APP兑巾,類似于OC中觀察者模式条获。本人做的項目藍牙設備傳輸數(shù)據(jù)都是通過通知實現(xiàn)的。
具體實現(xiàn)步驟
1蒋歌、工程導入CoreBluetooth.framework.
2帅掘、在類里面導入頭文件 CoreBluetooth/CoreBluetooth.h,添加代理堂油,創(chuàng)建中心對象
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
@interface ViewController (){
//系統(tǒng)藍牙設備管理對象修档,可以把他理解為主設備,通過他可以去掃描和連接外設
CBCentralManager *manager;
//用于保存被發(fā)現(xiàn)設備
NSMutableDictionary *discoverPeripherals;
}
這里一定要添加CBCentralManagerDelegate,CBPeripheralDelegate 2個代理府框,藍牙大部分的回調(diào)都是通過代理方法實現(xiàn)的吱窝。
//存放持有發(fā)現(xiàn)的設備,如果不持有設備會導致CBPeripheralDelegate方法不能正確回調(diào)
discoverPeripherals = [[NSMutableDictionary alloc] init];
//初始化并設置委托,最好一個線程的參數(shù)可以為nil迫靖,默認會就main線程
manager= [[CBCentralManager alloc] initWithDelegate:self queue:nil];
3院峡、掃描周圍藍牙設備
//此代理方法查看手機藍牙狀態(tài)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStateUnknown:
break;
case CBCentralManagerStateResetting:
break;
case CBCentralManagerStateUnsupported:
break;
case CBCentralManagerStateUnauthorized:
break;
case CBCentralManagerStatePoweredOff:
break;
case CBCentralManagerStatePoweredOn:
//只有藍牙開啟正常,才開始掃描周圍的外設
/*
第一個參數(shù)可以設置搜索條件系宜,nil就是掃描周圍所有的外設照激,掃描到外設后會進入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
*/
[manager scanForPeripheralsWithServices:nil options:nil];
break;
default:
break;
}
}
只有狀態(tài)為CBCentralManagerStatePoweredOn才能進行后續(xù)掃描,要不然掃描后不會走代理方法
//掃描到設備會進入此方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"當掃描到設備名稱:%@",peripheral.name);
//RSSI 藍牙信號強度
//這里可以根據(jù)設備名稱去連接你的設備
}
4盹牧、連接設備
//掃描到設備會進入此方法(還是上面這個方法??)
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"當掃描到設備名稱:%@",peripheral.name);
//RSSI 藍牙信號強度
//這里可以根據(jù)設備名稱去連接你的設備
//接下連接我們的測試設備俩垃,如果你沒有設備励幼,可以下載一個app叫l(wèi)ightbule的app去模擬一個設備
//這里自己去設置下連接規(guī)則,我設置的是P開頭的設備
if ([peripheral.name isEqualToString:@"XXXX"]){
//找到的設備必須持有它口柳,否則CBCentralManager中也不會保存peripheral苹粟,那么CBPeripheralDelegate中的方法也不會被調(diào)用!跃闹!
//我開始就掉進這個坑??????嵌削,可以用個容器裝著或者全局對象(你也可以用數(shù)組,看自己需求來)
[discoverPeripherals setObject:peripheral forKey:@"peripheral"];
/*
連接成功望艺,失敗掷贾,斷開會進入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設的委托
*/
//連接設備
[manager connectPeripheral:peripheral options:nil];
}
}
//連接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>連接到名稱為(%@)的設備-成功",peripheral.name);
//設置外設的代理,不設置的話連接成功不會走后續(xù)的方法了
[peripheral setDelegate:self];
//掃描外設Services荣茫,成功后會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
[peripheral discoverServices:nil];
}
//連接到Peripherals-失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@">>>連接到名稱為(%@)的設備-失敗,原因:%@",[peripheral name],[error localizedDescription]);
}
//Peripherals 斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@">>>外設連接斷開連接 %@: %@\n", [peripheral name], [error localizedDescription]);
持有設備想帅!持有設備!持有設備啡莉!重要的是說三遍8圩肌!咧欣!
5浅缸、掃描服務、特征
//掃描到Services
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
NSLog(@">>>掃描到服務:%@",peripheral.services);
if (error)
{
NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"服務UUID = %@",service.UUID);
//掃描每個service的Characteristics魄咕,掃描到后會進入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
[peripheral discoverCharacteristics:nil forService:service];
}
}
//掃描到Characteristics
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error)
{
NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics)
{
//這里根據(jù)服務和特征的UUID去進行數(shù)據(jù)交互
//訂閱特征
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
//往設備寫寫數(shù)據(jù) xxx為NSData類型數(shù)據(jù)
[peripheral writeValue:xxx forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]
}
//獲取Characteristic的值衩椒,讀到數(shù)據(jù)會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
{
[peripheral readValueForCharacteristic:characteristic];
}
}
//搜索Characteristic的Descriptors,讀到數(shù)據(jù)會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
上面2個方法掃描主要是讀取服務和特征的UUID哮兰,好進行下一步操作毛萌。服務和特征都有UUID,UUID好比是類的名稱喝滞,屬性名稱阁将,特征的值就像屬性的值
6、讀取數(shù)據(jù)
//獲取的charateristic的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出characteristic的UUID和值
//!注意右遭,value的類型是NSData做盅,具體開發(fā)時,會根據(jù)外設協(xié)議制定的方式去解析數(shù)據(jù)
NSLog(@"設備數(shù)據(jù) == %@窘哈,%@吹榴,%@",characteristic.service.UUID,characteristic.UUID,characteristic.value)
}
上面就是讀取特征具體的值,根據(jù)協(xié)議去解析數(shù)據(jù)滚婉,并轉(zhuǎn)化為你需要的數(shù)據(jù)類型图筹。
下面2個方法感覺用處不大,實際開發(fā)中满哪,每個特征的的作用開發(fā)文檔會寫明的婿斥,或者硬件工程師會告訴你的
//搜索到Characteristic的Descriptors
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出Characteristic和他的Descriptors
//NSLog(@"特征 uuid:%@",characteristic.UUID);
for (CBDescriptor *d in characteristic.descriptors) {
NSLog(@"特征描述 uuid:%@",d.UUID);
}
}
//獲取到Descriptors的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
//打印出DescriptorsUUID 和value
//這個descriptor都是對于characteristic的描述,一般都是字符串哨鸭,所以這里我們轉(zhuǎn)換成字符串去解析
NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
//寫入數(shù)據(jù)回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (error) {
NSLog(@"寫入數(shù)據(jù)失敗");
return;
}
NSLog(@"寫入數(shù)據(jù)成功 %@",characteristic);
}
//訂閱回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"訂閱特征 %@失敗",characteristic);
return;
}
NSLog(@"訂閱特征 %@成功",characteristic);
}
//停止掃描并斷開連接---自己寫的不是代理??
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止掃描
[centralManager stopScan];
//斷開連接
[centralManager cancelPeripheralConnection:peripheral];
}
我自己做的藍牙設備都是通過訂閱某個特征從而獲取數(shù)據(jù)的民宿。
其中需要用到UUID的比較,留下個CBUUID轉(zhuǎn)NSString的方法
- (NSString *)representativeString
{
NSData *data = [self data];
NSUInteger bytesToConvert = [data length];
const unsigned char *uuidBytes = [data bytes];
NSMutableString *outputString = [NSMutableString stringWithCapacity:16];
for (NSUInteger currentByteIndex = 0; currentByteIndex < bytesToConvert; currentByteIndex++)
{
switch (currentByteIndex)
{
case 3:
case 5:
case 7:
case 9:[outputString appendFormat:@"%02x-", uuidBytes[currentByteIndex]]; break;
default:[outputString appendFormat:@"%02x", uuidBytes[currentByteIndex]];
}
}
return outputString;
}
第一次做藍牙設備的開發(fā)像鸡,遇到過幾個坑活鹰,總結下:
1、沒有持有CBCentralManager對象只估,導致調(diào)用連接方法后不走是否成功的代理方法志群。
2、忘記設置外設peripheral的代理了蛔钙,導致不走后面掃描服務的代理方法
3锌云、由于這個項目是幫別人做的,所有沒有文檔吁脱,只有一份安卓源碼和安卓同事整理出來的一份簡單文檔桑涎,往哪個特征寫數(shù)據(jù),哪個特征讀數(shù)據(jù)兼贡,都不清楚攻冷,把我搞得半死。其中有設備是往同一個特征讀寫數(shù)據(jù)遍希;有往一個特征寫數(shù)據(jù)等曼,另外一個讀數(shù)據(jù)的??。我一個個去試凿蒜,頭疼啊禁谦。
項目背景
5個醫(yī)療類藍牙設備:心電儀,血糖儀废封,血壓儀枷畏,血氧儀,體脂稱虱饿。
本人第一次寫博客拥诡,第一次做藍牙開發(fā),如有錯誤氮发,還望指正渴肉!萬分謝謝!(內(nèi)容有參考網(wǎng)絡文章爽冕,侵刪3鸺馈)