iOS藍(lán)牙開(kāi)發(fā)(一)藍(lán)牙相關(guān)基礎(chǔ)知識(shí)
藍(lán)牙常見(jiàn)名稱(chēng)和縮寫(xiě)
MFI ======= make for ipad ,iphone, itouch 專(zhuān)們?yōu)樘O(píng)果設(shè)備制作的設(shè)備
BLE ==== buletouch low energy,藍(lán)牙4.0設(shè)備因?yàn)榈秃碾?,所以也叫做BLE
peripheral,central == 外設(shè)和中心,發(fā)起連接的時(shí)central,被連接的設(shè)備為perilheral
service and characteristic === 服務(wù)和特征 每個(gè)設(shè)備會(huì)提供服務(wù)和特征,類(lèi)似于服務(wù)端的api,但是機(jī)構(gòu)不同。每個(gè)外設(shè)會(huì)有很多服務(wù),每個(gè)服務(wù)中包含很多字段,這些字段的權(quán)限一般分為 讀read,寫(xiě)write,通知notiy幾種,就是我們連接設(shè)備后具體需要操作的內(nèi)容。
Description 每個(gè)characteristic可以對(duì)應(yīng)一個(gè)或多個(gè)Description用戶(hù)描述characteristic的信息或?qū)傩?/p>
MFI === 開(kāi)發(fā)使用ExternalAccessory 框架
4.0 BLE === 開(kāi)發(fā)使用CoreBluetooth 框架
藍(lán)牙基礎(chǔ)知識(shí)
CoreBluetooth框架的核心其實(shí)是兩個(gè)東西,peripheral和central, 可以理解成外設(shè)和中心。對(duì)應(yīng)他們分別有一組相關(guān)的API和類(lèi)

這兩組api分別對(duì)應(yīng)不同的業(yè)務(wù)場(chǎng)景,左側(cè)叫做中心模式,就是以你的app作為中心,連接其他的外設(shè)的場(chǎng)景,而右側(cè)稱(chēng)為外設(shè)模式,使用手機(jī)作為外設(shè)別其他中心設(shè)備操作的場(chǎng)景。
-
服務(wù)和特征,特征的屬性(service and characteristic):
每個(gè)設(shè)備都會(huì)有一些服務(wù),每個(gè)服務(wù)里面都會(huì)有一些特征,特征就是具體鍵值對(duì),提供數(shù)據(jù)的地方。每個(gè)特征屬性分為這么幾種:讀,寫(xiě),通知這么幾種方式。//objcetive c特征的定義枚舉 typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) { CBCharacteristicPropertyBroadcast = 0x01, CBCharacteristicPropertyRead = 0x02, CBCharacteristicPropertyWriteWithoutResponse = 0x04, CBCharacteristicPropertyWrite = 0x08, CBCharacteristicPropertyNotify = 0x10, CBCharacteristicPropertyIndicate = 0x20, CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40, CBCharacteristicPropertyExtendedProperties = 0x80, CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100, CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200 };
外設(shè)、服務(wù)、特征間的關(guān)系

藍(lán)牙中心模式流程
1\. 建立中心角色
2\. 掃描外設(shè)(discover)
3\. 連接外設(shè)(connect)
4\. 掃描外設(shè)中的服務(wù)和特征(discover)
- 4.1 獲取外設(shè)的services
- 4.2 獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值
5\. 與外設(shè)做數(shù)據(jù)交互(explore and interact)
6\. 訂閱Characteristic的通知
7\. 斷開(kāi)連接(disconnect)
藍(lán)牙外設(shè)模式流程
1\. 啟動(dòng)一個(gè)Peripheral管理對(duì)象
2\. 本地Peripheral設(shè)置服務(wù),特性,描述,權(quán)限等等
3\. Peripheral發(fā)送廣告
4\. 設(shè)置處理訂閱、取消訂閱、讀characteristic、寫(xiě)characteristic的委托方法
藍(lán)牙設(shè)備狀態(tài)
1\. 待機(jī)狀態(tài)(standby):設(shè)備沒(méi)有傳輸和發(fā)送數(shù)據(jù),并且沒(méi)有連接到任何設(shè)
2\. 廣播狀態(tài)(Advertiser):周期性廣播狀態(tài)
3\. 掃描狀態(tài)(Scanner):主動(dòng)尋找正在廣播的設(shè)備
4\. 發(fā)起鏈接狀態(tài)(Initiator):主動(dòng)向掃描設(shè)備發(fā)起連接。
5\. 主設(shè)備(Master):作為主設(shè)備連接到其他設(shè)備。
6\. 從設(shè)備(Slave):作為從設(shè)備連接到其他設(shè)備。
藍(lán)牙設(shè)備的五種工作狀態(tài)
- 準(zhǔn)備(standby)
- 廣播(advertising)
- 監(jiān)聽(tīng)掃描(Scanning
- 發(fā)起連接(Initiating)
- 已連接(Connected)
藍(lán)牙和版本的使用限制
- 藍(lán)牙2.0 === 越獄設(shè)備
- 藍(lán)牙4.0 === IOS6 以上
- MFI認(rèn)證設(shè)備(Make For ipod/ipad/iphone) === 無(wú)限制
名詞解釋
- GAAT : Generic Attribute Profile , GATT配置文件是一個(gè)通用規(guī)范,用于在BLE鏈路上發(fā)送和接收被稱(chēng)為“屬性”的數(shù)據(jù)塊。目前所有的BLE應(yīng)用都基于GATT。 藍(lán)牙SIG規(guī)定了許多低功耗設(shè)備的配置文件。配置文件是設(shè)備如何在特定的應(yīng)用程序中工作的規(guī)格說(shuō)明。注意一個(gè)設(shè)備可以實(shí)現(xiàn)多個(gè)配置文件。例如,一個(gè)設(shè)備可能包括心率監(jiān)測(cè)儀和電量檢測(cè)。
- Characteristic 一個(gè)characteristic包括一個(gè)單一變量和0-n個(gè)用來(lái)描述characteristic變量的descriptor,characteristic可以被認(rèn)為是一個(gè)類(lèi)型,類(lèi) 似于類(lèi)。
- Descriptor Descriptor用來(lái)描述characteristic變量的屬性。例如,一個(gè)descriptor可以規(guī)定一個(gè)可讀的描述,或者一個(gè)characteristic變量可接受的范圍,或者一個(gè)characteristic變量特定的測(cè)量單位。 Service service是characteristic的集合。例如,你可能有一個(gè)叫“Heart Rate Monitor(心率監(jiān)測(cè)儀)”的service,它包括了很多characteristics,如“heart rate measurement(心率測(cè)量)”等。你可以在bluetooth.org 找到一個(gè)目前支持的基于GATT的配置文件和服務(wù)列表。
ios連接外設(shè)的代碼實(shí)現(xiàn)流程
- 建立中心角色
- 掃描外設(shè)(discover)
- 連接外設(shè)(connect)
- 掃描外設(shè)中的服務(wù)和特征(discover)
- 4.1 獲取外設(shè)的services
- 4.2 獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值
- 與外設(shè)做數(shù)據(jù)交互(explore and interact)
- 訂閱Characteristic的通知
- 斷開(kāi)連接(disconnect)
準(zhǔn)備環(huán)境
1 xcode
2 開(kāi)發(fā)證書(shū)和手機(jī)(藍(lán)牙程序需要使用使用真機(jī)調(diào)試,使用模擬器也可以調(diào)試,但是方法很蛋疼,我會(huì)放在最后說(shuō))
3 藍(lán)牙外設(shè)
實(shí)現(xiàn)步驟
1 導(dǎo)入CoreBluetooth頭文件,建立主設(shè)備管理類(lèi),設(shè)置主設(shè)備委托
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController : UIViewController<CBCentralManagerDelegate>
@interface ViewController (){
//系統(tǒng)藍(lán)牙設(shè)備管理對(duì)象,可以把他理解為主設(shè)備,通過(guò)他,可以去掃描和鏈接外設(shè)
CBCentralManager *manager;
//用于保存被發(fā)現(xiàn)設(shè)備
NSMutableArray *peripherals;
}
- (void)viewDidLoad {
[super viewDidLoad];
/*
設(shè)置主設(shè)備的委托,CBCentralManagerDelegate
必須實(shí)現(xiàn)的:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;//主設(shè)備狀態(tài)改變的委托,在初始化CBCentralManager的適合會(huì)打開(kāi)設(shè)備,只有當(dāng)設(shè)備正確打開(kāi)后才能使用
其他選擇實(shí)現(xiàn)的委托中比較重要的:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外設(shè)的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設(shè)成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開(kāi)外設(shè)的委托
*/
//初始化并設(shè)置委托和線程隊(duì)列,最好一個(gè)線程的參數(shù)可以為nil,默認(rèn)會(huì)就main線程
manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
2 掃描外設(shè)(discover),掃描外設(shè)的方法我們放在centralManager成功打開(kāi)的委托中,因?yàn)橹挥性O(shè)備成功打開(kāi),才能開(kāi)始掃描,否則會(huì)報(bào)錯(cuò)。
-(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");
//開(kāi)始掃描周?chē)耐庠O(shè)
/*
第一個(gè)參數(shù)nil就是掃描周?chē)械耐庠O(shè),掃描到外設(shè)后會(huì)進(jìn)入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
*/
[manager scanForPeripheralsWithServices:nil options:nil];
break;
default:
break;
}
}
//掃描到設(shè)備會(huì)進(jìn)入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"當(dāng)掃描到設(shè)備:%@",peripheral.name);
//接下來(lái)可以連接設(shè)備
}
3 連接外設(shè)(connect)
//掃描到設(shè)備會(huì)進(jìn)入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
//接下連接我們的測(cè)試設(shè)備,如果你沒(méi)有設(shè)備,可以下載一個(gè)app叫l(wèi)ightbule的app去模擬一個(gè)設(shè)備
//這里自己去設(shè)置下連接規(guī)則,我設(shè)置的是P開(kāi)頭的設(shè)備
if ([peripheral.name hasPrefix:@"P"]){
/*
一個(gè)主設(shè)備最多能連7個(gè)外設(shè),每個(gè)外設(shè)最多只能給一個(gè)主設(shè)備連接,連接成功,失敗,斷開(kāi)會(huì)進(jìn)入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設(shè)成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開(kāi)外設(shè)的委托
*/
//找到的設(shè)備必須持有它,否則CBCentralManager中也不會(huì)保存peripheral,那么CBPeripheralDelegate中的方法也不會(huì)被調(diào)用?。? [peripherals addObject:peripheral];
//連接設(shè)備
[manager connectPeripheral:peripheral options:nil];
}
}
//連接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>連接到名稱(chēng)為(%@)的設(shè)備-成功",peripheral.name);
}
//連接到Peripherals-失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@">>>連接到名稱(chēng)為(%@)的設(shè)備-失敗,原因:%@",[peripheral name],[error localizedDescription]);
}
//Peripherals斷開(kāi)連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@">>>外設(shè)連接斷開(kāi)連接 %@: %@\n", [peripheral name], [error localizedDescription]);
}
有一點(diǎn)非常容易出錯(cuò),大家請(qǐng)注意。在 didDiscoverPeripheral這個(gè)委托中有這一行
//找到的設(shè)備必須持有它,否則CBCentralManager中也不會(huì)保存peripheral,那么CBPeripheralDelegate中的方法也不會(huì)被調(diào)用?。?br>
[peripherals addObject:peripheral];
請(qǐng)?zhí)貏e注意,如果不保存,會(huì)影響到后面的方法執(zhí)行,這個(gè)地方很多人出錯(cuò),在我的藍(lán)牙交流群中每天幾乎都會(huì)因?yàn)檫@個(gè)問(wèn)題導(dǎo)致無(wú)法連接和對(duì)外設(shè)后續(xù)的操作。
大家也可以看一下這個(gè)委托在xcode中的說(shuō)明,重點(diǎn)看@discussion中的內(nèi)容,里面特別指出了需要retained對(duì)象。
/*!
- @method centralManager:didDiscoverPeripheral:advertisementData:RSSI:
- @param central The central manager providing this update.
- @param peripheral A <code>CBPeripheral</code> object.
- @param advertisementData A dictionary containing any advertisement and scan response data.
- @param RSSI The current RSSI of <i>peripheral</i>, in dBm. A value of <code>127</code> is reserved and indicates the RSSI
was not available.- @discussion This method is invoked while scanning, upon the discovery of <i>peripheral</i> by <i>central</i>. A discovered peripheral must
be retained in order to use it; otherwise, it is assumed to not be of interest and will be cleaned up by the central manager. Fora list of <i>advertisementData</i> keys, see {@link CBAdvertisementDataLocalNameKey} and other similar constants.- @seealso CBAdvertisementData.h
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
4掃描外設(shè)中的服務(wù)和特征(discover)
設(shè)備連接成功后,就可以掃描設(shè)備的服務(wù)了,同樣是通過(guò)委托形式,掃描到結(jié)果后會(huì)進(jìn)入委托方法。但是這個(gè)委托已經(jīng)不再是主設(shè)備的委托(CBCentralManagerDelegate),而是外設(shè)的委托(CBPeripheralDelegate),這個(gè)委托包含了主設(shè)備與外設(shè)交互的許多 回叫方法,包括獲取services,獲取characteristics,獲取characteristics的值,獲取characteristics的Descriptor,和Descriptor的值,寫(xiě)數(shù)據(jù),讀rssi,用通知的方式訂閱數(shù)據(jù)等等。
4.1獲取外設(shè)的services
//連接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>連接到名稱(chēng)為(%@)的設(shè)備-成功",peripheral.name);
//設(shè)置的peripheral委托CBPeripheralDelegate
//@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
[peripheral setDelegate:self];
//掃描外設(shè)Services,成功后會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
[peripheral discoverServices:nil];
}
//掃描到Services
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
// NSLog(@">>>掃描到服務(wù):%@",peripheral.services);
if (error)
{
NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"%@",service.UUID);
//掃描每個(gè)service的Characteristics,掃描到后會(huì)進(jìn)入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
[peripheral discoverCharacteristics:nil forService:service];
}
}
4.2獲取外設(shè)的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值
//掃描到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)
{
NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
}
//獲取Characteristic的值,讀到數(shù)據(jù)會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
{
[peripheral readValueForCharacteristic:characteristic];
}
}
//搜索Characteristic的Descriptors,讀到數(shù)據(jù)會(huì)進(jìn)入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
//獲取的charateristic的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出characteristic的UUID和值
//!注意,value的類(lèi)型是NSData,具體開(kāi)發(fā)時(shí),會(huì)根據(jù)外設(shè)協(xié)議制定的方式去解析數(shù)據(jù)
NSLog(@"characteristic uuid:%@ value:%@",characteristic.UUID,characteristic.value);
}
//搜索到Characteristic的Descriptors
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出Characteristic和他的Descriptors
NSLog(@"characteristic uuid:%@",characteristic.UUID);
for (CBDescriptor *d in characteristic.descriptors) {
NSLog(@"Descriptor uuid:%@",d.UUID);
}
}
//獲取到Descriptors的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
//打印出DescriptorsUUID 和value
//這個(gè)descriptor都是對(duì)于characteristic的描述,一般都是字符串,所以這里我們轉(zhuǎn)換成字符串去解析
NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
5 把數(shù)據(jù)寫(xiě)到Characteristic中
//寫(xiě)數(shù)據(jù)
-(void)writeCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic
value:(NSData *)value{
//打印出 characteristic 的權(quán)限,可以看到有很多種,這是一個(gè)NS_OPTIONS,就是可以同時(shí)用于好幾個(gè)值,常見(jiàn)的有read,write,notify,indicate,知知道這幾個(gè)基本就夠用了,前連個(gè)是讀寫(xiě)權(quán)限,后兩個(gè)都是通知,兩種不同的通知方式。
/*
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
CBCharacteristicPropertyBroadcast = 0x01,
CBCharacteristicPropertyRead = 0x02,
CBCharacteristicPropertyWriteWithoutResponse = 0x04,
CBCharacteristicPropertyWrite = 0x08,
CBCharacteristicPropertyNotify = 0x10,
CBCharacteristicPropertyIndicate = 0x20,
CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
CBCharacteristicPropertyExtendedProperties = 0x80,
CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,
CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200
};
*/
NSLog(@"%lu", (unsigned long)characteristic.properties);
//只有 characteristic.properties 有write的權(quán)限才可以寫(xiě)
if(characteristic.properties & CBCharacteristicPropertyWrite){
/*
最好一個(gè)type參數(shù)可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區(qū)別是是否會(huì)有反饋
*/
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"該字段不可寫(xiě)!");
}
}
6 訂閱Characteristic的通知
//設(shè)置通知
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
//設(shè)置通知,數(shù)據(jù)通知會(huì)進(jìn)入:didUpdateValueForCharacteristic方法
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//取消通知
-(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
7 斷開(kāi)連接(disconnect)
//停止掃描并斷開(kāi)連接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止掃描
[centralManager stopScan];
//斷開(kāi)連接
[centralManager cancelPeripheralConnection:peripheral];
}
本文轉(zhuǎn)載自
最后推薦一個(gè)框架# BabyBluetooth