在上家公司待了2年,一直在做藍(lán)牙相關(guān)的工作,也一直沒有來總結(jié)關(guān)于藍(lán)牙的文章,昨天在交流群中的小伙伴問到藍(lán)牙相關(guān)問題,幫他看了看指出了問題,同時(shí)也發(fā)現(xiàn)很多東西長時(shí)間不看也都快忘記了,今天在這里總結(jié)一下,以防以后用時(shí)候重新找資料的煩惱,同時(shí)也希望能幫到有需要的朋友!
下面分享中 部分內(nèi)容查看了BabyBluetooth作者的分享,部分根據(jù)當(dāng)時(shí)公司的業(yè)務(wù)需求總結(jié)實(shí)戰(zhàn).希望能對(duì)有需要的人有所幫助
講解分四大模塊
- 基礎(chǔ)知識(shí)了解
- app作為主設(shè)備
- app作為從設(shè)備
- BabyBluetooth入門
- 實(shí)戰(zhàn)OTA空中升級(jí)
一.基礎(chǔ)知識(shí)了解
藍(lán)牙4.0的設(shè)備由于耗電低,so也稱作為BLE(Bluetooth Low Energy)百度百科有關(guān)于藍(lán)牙歷史的介紹
peripheral 外設(shè),被連接的設(shè)備為perilheralcentral 發(fā)起連接的時(shí)centralservice and characteristic 服務(wù)和特征 每個(gè)設(shè)備都會(huì)提供服務(wù)和特征,類似于服務(wù)端的api,但是由于結(jié)構(gòu)不 同,每個(gè)外設(shè)會(huì)有很多的服務(wù),每個(gè)服務(wù)中又包含很多字段,這些字段的權(quán)限一般分為 讀read,寫write,通知 notiy幾種,就是我們連接設(shè)備后具體需要操作的內(nèi)容。Description 每個(gè)characteristic可以對(duì)應(yīng)一個(gè)或多個(gè)Description用戶描述characteristic的信息或?qū)傩?Characteristic 一個(gè)characteristic包括一個(gè)單一變量和0-n個(gè)用來描述 characteristic變量的descriptor,characteristic可以被認(rèn)為是一個(gè)類型,類 似于類。Descriptor Descriptor用來描述characteristic變量的屬性。例如,一個(gè)descriptor可以規(guī)定一個(gè)可讀的描述,或者一個(gè)characteristic變量可接受的 范圍,或者一個(gè)characteristic變量特定的測(cè)量單位。Service service是characteristic的集合
* BLE中的開發(fā)要使用CoreBluetooth框架
CoreBluetooth框架的核心其實(shí)是兩個(gè)東西,peripheral和central, 可以理解成外設(shè)和中心。 對(duì)應(yīng)他們分別有一組相關(guān)的API和類

以上這兩組分別對(duì)應(yīng)不同的業(yè)務(wù)場(chǎng)景,左邊叫做中心模式,就是你的app作為中心,連接其他的外設(shè)的場(chǎng)景,而右邊稱為外設(shè)模式,使用手機(jī)作為 外設(shè)別其他中心設(shè)備操作的場(chǎng)景。每個(gè)設(shè)備都會(huì)有一些服務(wù),每個(gè)服務(wù)里面都會(huì)有一些特征,特征就是具體鍵值對(duì),提供數(shù)據(jù)的地方。每個(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
};

藍(lán)牙設(shè)備的幾種狀態(tài):
- 準(zhǔn)備(standby)
- 廣播(advertising)
- 監(jiān)聽掃描(Scanning
- 發(fā)起連接(Initiating)
- 已連接(Connected)
作為中心模式流程:
- 建立中心角色
- 掃描外設(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的通知
- 斷開連接(disconnect)
作為外設(shè)模式流程:
- 啟動(dòng)一個(gè)Peripheral管理對(duì)象
- 本地Peripheral設(shè)置服務(wù),特性,描述,權(quán)限等等
- Peripheral發(fā)送廣告
- 設(shè)置處理訂閱、取消訂閱、讀characteristic、寫characteristic的委托方法
部分知識(shí)參考 GATT Profile 簡(jiǎn)介 有興趣的同學(xué)可以閱讀
二. app作為主設(shè)備 掃描/連接 外設(shè) 的 相關(guān)代碼實(shí)現(xiàn)
上面說了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的通知
- 斷開連接(disconnect)
下面要開始動(dòng)手寫代碼啦(??)
藍(lán)牙需要真機(jī)調(diào)試,所以必須要有真機(jī), 下載Xcode 一個(gè)藍(lán)牙外設(shè)
// 1. 導(dǎo)入 CoreBluetooth 框架,和頭文件
// 1.1系統(tǒng)藍(lán)牙設(shè)備管理對(duì)象,可以把他理解為主設(shè)備,通過他,可以去掃描和鏈接外設(shè)
CBCentralManager *manager;
// 1.2 用于保存被發(fā)現(xiàn)設(shè)備
NSMutableArray *peripherals;
// 1.3 設(shè)置主設(shè)備的委托,CBCentralManagerDelegate
// 1.4 必須實(shí)現(xiàn)的: //主設(shè)備狀態(tài)改變的委托,在初始化CBCentralManager的適合會(huì)打開設(shè)備,只有當(dāng)設(shè)備正確打開后才能使用
- (void)centralManagerDidUpdateState:(CBCentralManager *)central; //其他選擇實(shí)現(xiàn)的委托中比較重要的:找到外設(shè)的委托
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
//1.5 連接外設(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;
//初始化并設(shè)置委托和線程隊(duì)列,最好一個(gè)線程的參數(shù)可以為nil,默認(rèn)會(huì)就main線程
manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
- 方法中 當(dāng)設(shè)備開關(guān)藍(lán)牙 都會(huì)走這個(gè)回調(diào)
-(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");
//開始掃描周圍的外設(shè)
/*
第一個(gè)參數(shù)nil就是掃描周圍所有的外設(shè),掃描到外設(shè)后會(huì)進(jìn)入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
第二個(gè)參數(shù)可以添加一些option,來增加精確的查找范圍, 如 :
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey,
nil];
[manager scanForPeripheralsWithServices:nil options:options];
*/
[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);
//接下來可以連接設(shè)備
}
- 連接外設(shè)(connect)
//掃描到設(shè)備會(huì)進(jìn)入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
//接下連接我們的測(cè)試設(shè)備,如果你沒有設(shè)備,可以下載一個(gè)app叫l(wèi)ightbule的app去模擬一個(gè)設(shè)備
//這里自己去設(shè)置下連接規(guī)則,我設(shè)置的是P開頭的設(shè)備
if ([peripheral.name hasPrefix:@"P"]){
// 一個(gè)主設(shè)備最多能連7個(gè)外設(shè),每個(gè)外設(shè)最多只能給一個(gè)主設(shè)備連接,連接成功,失敗,斷開會(huì)進(jìn)入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設(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(@"連接到名稱為(%@)的設(shè)備-成功",peripheral.name);
}
//連接到Peripherals-失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"連接到名稱為(%@)的設(shè)備-失敗,原因:%@",[peripheral name],[error localizedDescription]);
}
//Peripherals斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"外設(shè)連接斷開連接 %@ \n", [peripheral name]);
}
注意 : [peripherals addObject:peripheral]; 這句代碼是要手動(dòng)持有外設(shè),必須保存!
- 獲取外設(shè)的服務(wù)和特征
<連接外設(shè)成功后,就要開始獲取 設(shè)備的服務(wù)了,都有相應(yīng)的方法和回調(diào),掃描到結(jié)果后會(huì)進(jìn)入delegate方法。但是這個(gè)委托已經(jīng)不再是主設(shè)備的委托(CBCentralManagerDelegate),而是外設(shè)的委托(CBPeripheralDelegate),這個(gè)委托包含了主設(shè)備與外設(shè)交互的許多 回調(diào)方法,包括獲取services,獲取characteristics,獲取characteristics的值,獲取characteristics的Descriptor,和Descriptor的值,寫數(shù)據(jù),讀rssi,用通知的方式訂閱數(shù)據(jù)等等。>
// ================== 獲取 外設(shè)的service ==============//
//連接到Peripherals-成功回調(diào)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"連接到名稱為(%@)的設(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{
// 獲取service時(shí)候也可以設(shè)置option 如:
// NSMutableArray *serviceUUIDs = [NSMutableArray array ];
// CBUUID *cbuuid = [CBUUID UUIDWithString:[NSString stringWithFormat:@"%x",Ble_Device_Service]];
// [serviceUUIDs addObject:cbuuid];
// [peripheral discoverServices:serviceUUIDs];
// 添加指定條件可以 提高效率
[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];
}
}
// ==========獲取外設(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的類型是NSData,具體開發(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);
}
- 把數(shù)據(jù)寫到 Characteristic 中
//寫數(shù)據(jù)
-(void)writeCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic
value:(NSData *)value{
//打印出 characteristic 的權(quán)限,可以看到有很多種,這是一個(gè)NS_OPTIONS,就是可以同時(shí)用于好幾個(gè)值,常見的有read,write,notify,indicate,知知道這幾個(gè)基本就夠用了,前連個(gè)是讀寫權(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)限才可以寫
if(characteristic.properties & CBCharacteristicPropertyWrite){
/*
最好一個(gè)type參數(shù)可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區(qū)別是是否會(huì)有反饋
*/
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"該字段不能寫!");
}
}
- 訂閱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];
}
- 斷開連接
//停止掃描并斷開連接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止掃描
[centralManager stopScan];
//斷開連接
[centralManager cancelPeripheralConnection:peripheral];
}
三. app作為外設(shè)被主設(shè)備連接
這里使用app作為一個(gè)peripheral,被其它的central連接
peripheral連接流程:
- 打開peripheralManager,設(shè)置peripheralManager的delegate
- 創(chuàng)建characteristics,characteristics的description 創(chuàng)建service,把characteristics添加到service中,再把service添加到peripheralManager中
- 開啟廣播advertising
- 對(duì)central的操作進(jìn)行響應(yīng)
- 4.1 讀characteristics請(qǐng)求
- 4.2 寫characteristics請(qǐng)求
- 4.4 訂閱和取消訂閱characteristics
具體步驟核心代碼:
<打開peripheralManager,設(shè)置peripheralManager的委托><初始化peripheralManager, 這里的peripheralManager和CBCentralManager 很相似>
peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
<創(chuàng)建characteristics 創(chuàng)建service,把characteristics添加到service 然后把service添加到peripheralManager>
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
// 在這里判斷藍(lán)牙的狀態(tài), 因?yàn)樗{(lán)牙打開成功后才能配置service和characteristics
[self config];
}
<配置相關(guān)service和>
//配置藍(lán)牙的服務(wù)和特征
-(void)config{
//特征字段描述
CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];
/*
可以通知的Characteristic
properties:CBCharacteristicPropertyNotify
permissions CBAttributePermissionsReadable
*/
CBMutableCharacteristic *notiyCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:notiyCharacteristicUUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
/*
可讀寫的characteristics
properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead
permissions CBAttributePermissionsReadable | CBAttributePermissionsWriteable
*/
CBMutableCharacteristic *readwriteCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readwriteCharacteristicUUID] properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
//設(shè)置description
CBMutableDescriptor *readwriteCharacteristicDescription1 = [[CBMutableDescriptor alloc]initWithType: CBUUIDCharacteristicUserDescriptionStringUUID value:@"name"];
[readwriteCharacteristic setDescriptors:@[readwriteCharacteristicDescription1]];
/*
只讀的Characteristic
properties:CBCharacteristicPropertyRead
permissions CBAttributePermissionsReadable
*/
CBMutableCharacteristic *readCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readCharacteristicUUID] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];
//service1初始化并加入兩個(gè)characteristics
CBMutableService *service1 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];
[service1 setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];
//service2初始化并加入一個(gè)characteristics
CBMutableService *service2 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID2] primary:YES];
[service2 setCharacteristics:@[readCharacteristic]];
//添加后就會(huì)調(diào)用代理的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
[peripheralManager addService:service1];
[peripheralManager addService:service2];
}
<開啟廣播>
//perihpheral添加了service
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
if (error == nil) {
serviceNum++;
}
//因?yàn)槲覀兲砑恿?個(gè)服務(wù),所以想兩次都添加完成后才去發(fā)送廣播
if (serviceNum==2) {
//添加服務(wù)后可以在此向外界發(fā)出通告 調(diào)用完這個(gè)方法后會(huì)調(diào)用代理的
//(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
[peripheralManager startAdvertising:@{
CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:ServiceUUID1],[CBUUID UUIDWithString:ServiceUUID2]],
CBAdvertisementDataLocalNameKey : LocalNameKey
}
];
}
}
//peripheral開始發(fā)送advertising
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
NSLog(@"in peripheralManagerDidStartAdvertisiong");
}
<對(duì)central的操作進(jìn)行響應(yīng)>
1 讀characteristics請(qǐng)求 2 寫characteristics請(qǐng)求 3 訂閱和取消訂閱characteristics
//訂閱characteristics
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"訂閱了 %@的數(shù)據(jù)",characteristic.UUID);
//每秒執(zhí)行一次給主設(shè)備發(fā)送一個(gè)當(dāng)前時(shí)間的秒數(shù)
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendData:) userInfo:characteristic repeats:YES];
}
//取消訂閱characteristics
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"取消訂閱 %@的數(shù)據(jù)",characteristic.UUID);
//取消回應(yīng)
[timer invalidate];
}
//發(fā)送數(shù)據(jù),發(fā)送當(dāng)前時(shí)間的秒數(shù)
-(BOOL)sendData:(NSTimer *)t {
CBMutableCharacteristic *characteristic = t.userInfo;
NSDateFormatter *dft = [[NSDateFormatter alloc]init];
[dft setDateFormat:@"ss"];
NSLog(@"%@",[dft stringFromDate:[NSDate date]]);
//執(zhí)行回應(yīng)Central通知數(shù)據(jù)
return [peripheralManager updateValue:[[dft stringFromDate:[NSDate date]] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:nil];
}
//讀characteristics請(qǐng)求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
NSLog(@"didReceiveReadRequest");
//判斷是否有讀數(shù)據(jù)的權(quán)限
if (request.characteristic.properties & CBCharacteristicPropertyRead) {
NSData *data = request.characteristic.value;
[request setValue:data];
//對(duì)請(qǐng)求作出成功響應(yīng)
[peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}else{
[peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
//寫characteristics請(qǐng)求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
NSLog(@"didReceiveWriteRequests");
CBATTRequest *request = requests[0];
//判斷是否有寫數(shù)據(jù)的權(quán)限
if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
//需要轉(zhuǎn)換成CBMutableCharacteristic對(duì)象才能進(jìn)行寫值
CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;
c.value = request.value;
[peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}else{
[peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
四. 開源庫 BabyBluetooth介紹
有需要的同學(xué)可以看看BabyBluetooth源碼
BabyBluetooth的優(yōu)缺點(diǎn)
BabyBluetooth使用block方法,可以一定程度上節(jié)省開發(fā)時(shí)間,節(jié)約成本,畢竟直接用官方的框架寫還是比較麻煩的,
當(dāng)然這還要根據(jù)自己的實(shí)際情況來,當(dāng)時(shí)由于我所在公司需要自己的SDK,根據(jù)當(dāng)時(shí)的業(yè)務(wù)情況,只能我自己來寫了,
而BabyBluetooth鏈?zhǔn)降恼Z法寫起來比較清爽. 如果公司有一些業(yè)務(wù)上的需求BabyBluetooth沒有實(shí)現(xiàn),
等待作者更新是不太現(xiàn)實(shí)的,這時(shí)候自己來動(dòng)手開發(fā)一個(gè)吧!
使用步驟可以參考 :
iOS藍(lán)牙篇之BabyBluetooth詳解
iOS藍(lán)牙開發(fā):解析BabyBluetooth
iOS 藍(lán)牙庫BabyBluetooth的使用筆記
五. OTA(空中升級(jí)實(shí)戰(zhàn))
當(dāng)時(shí)的業(yè)務(wù)需求是 :
讀取地鎖當(dāng)前硬件的版本號(hào),判斷是否需要升級(jí)如果需要升級(jí),手機(jī)連接地鎖后給地鎖傳輸腳本給地鎖中的硬件進(jìn)行升級(jí)
<讀取硬件版本號(hào) 要連接上外設(shè)后 讀取的characteristic的值>
在回調(diào)方法 :
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
中處理

- 空中升級(jí)
這里會(huì)借助一個(gè)第三方的開源庫 : Nordic GitHub
DFUFirmware *selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:self.filePath];
self.initiator = [[DFUServiceInitiator alloc] initWithCentralManager: self.centralManager target:self.peripheral];
[self.initiator withFirmwareFile:selectedFirmware];
self.initiator.delegate = self; // - to be informed about current state and errors
self.initiator.logger = self;
self.initiator.progressDelegate = self;
self.dfuController = [self.initiator start];
??需要的代理:
#pragma mark DFUServiceDelegate
- (void)onUploadProgress:(NSInteger)part totalParts:(NSInteger)totalParts progress:(NSInteger)progress currentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond{
NSLog(@" %s %.2ld",__func__,(long)progress);
[self.delegate onFSMOtaUpgradeProgress:progress];
}
- (void)didErrorOccur:(enum DFUError)error withMessage:(NSString *)message{
switch (error) {
case DFUErrorRemoteSuccess:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteSuccess"]];
break;
case DFUErrorRemoteInvalidState :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteInvalidState"]];
break;
case DFUErrorRemoteNotSupported :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteNotSupported"]];
break;
case DFUErrorRemoteDataExceedsLimit :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteDataExceedsLimit"]];
break;
case DFUErrorRemoteCrcError :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteCrcError"]];
break;
case DFUErrorRemoteOperationFailed:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteOperationFailed"]];
break;
case DFUErrorFileNotSpecified:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileNotSpecified"]];
break;
case DFUErrorFileInvalid :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileInvalid"]];
break;
case DFUErrorExtendedInitPacketRequired :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorExtendedInitPacketRequired"]];
break;
case DFUErrorInitPacketRequired :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorInitPacketRequired"]];
break;
case DFUErrorFailedToConnect :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFailedToConnect"]];
break;
case DFUErrorDeviceDisconnected:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceDisconnected"]];
break;
case DFUErrorServiceDiscoveryFailed:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorServiceDiscoveryFailed"]];
break;
case DFUErrorDeviceNotSupported :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceNotSupported"]];
break;
case DFUErrorReadingVersionFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReadingVersionFailed"]];
break;
case DFUErrorEnablingControlPointFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorEnablingControlPointFailed"]];
break;
case DFUErrorWritingCharacteristicFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorWritingCharacteristicFailed"]];
break;
case DFUErrorReceivingNotificationFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReceivingNotificationFailed"]];
break;
case DFUErrorUnsupportedResponse :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorUnsupportedResponse"]];
break;
case DFUErrorBytesLost :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorBytesLost"]];
break;
default:
break;
}
NSLog(@" %s 升級(jí)失敗 錯(cuò)誤的信息是: %@",__func__ ,message);
NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject];
[savePath stringByAppendingPathComponent:@"ble_app_wechat_debug_v1.1.zip"];
[self disConnectDevice:self.lockId];
NSError *err;
NSFileManager *fileMgr = [NSFileManager defaultManager];
[fileMgr removeItemAtPath:savePath error:&err];
}
#pragma mark LoggerDelegate 代理
- (void)logWith:(enum LogLevel)level message:(NSString * _Nonnull)message{
// NSLog(@"消息是 : %@",message);
}
- (void)didStateChangedTo:(enum DFUState)state{
switch (state) {
case DFUStateAborted:{
[self otaCallback:@"DFUStateAborted"];
break;
}
case DFUStateConnecting:{
[self otaCallback:@"DFUStateConnecting"];
break;
}
case DFUStateDisconnecting:{
[self otaCallback:@"DFUStateDisconnecting"];
break;
}
case DFUStateEnablingDfuMode:{
[self otaCallback:@"DFUStateEnablingDfuMode"];
break;
}
case DFUStateStarting:{
[self otaCallback:@"DFUStateStarting"];
break;
}
case DFUStateUploading:{
[self otaCallback:@"DFUStateUploading"];
break;
}
case DFUStateValidating:{
[self otaCallback:@"DFUStateValidating"];
break;
}
case DFUStateCompleted:{
[self.centralManager cancelPeripheralConnection:self.peripheral];
[self otaCallback:@"DFUStateCompleted"];
NSLog(@"%d",self.dfuController.paused);
break;
}
default:
break;
}
}
//OTA升級(jí)的回調(diào)
- (void)otaCallback:(NSString *)state{
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(onFSMOtaUpgradeResult:)])
{
[self.delegate onFSMOtaUpgradeResult:state];
}
});
}