上一篇文章介紹了藍牙的基本知識,這里我們具體說下,中心模式的應用場景。主設備(手機去掃描連接外設)發現外設服務和屬性,操作服務和屬性的應用。一般來說,外設(藍牙設備。比如智能手環之類的東西),會有硬件工程師開發好,并定義好設備提供的服務,每個服務對應的特征,每個特征的屬性(只讀、只寫、通知等等),本文例子的業務場景,就是用一手機APP去讀寫藍牙設備。
一、iOS連接外設的代碼實現流程
- 建立中心角色
- 掃描外設(discover)
- 連接外設(connect)
- 掃描外設中的服務和特征(discover)
- 4.1 獲取外設的services
- 4.2 獲取外設的Characteristics,獲取Characteristics的值,獲取Characteristics的Descriptor和Descriptor的值
- 與外設做數據交互(explore and interact)
- 訂閱Characteristic的通知
- 斷開連接(disconnect)
二、準備環境
- xcode
- 開發證書和手機
3.藍牙外設
三、實現步驟
1.導入CoreBluetooth頭文件,建立主設備管理類,設置主設備代理
#import <CoreBluetooth/CoreBluetooth.h>
@interface BeCentralVewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
@interface BeCentralVewController (){
//系統藍牙設備管理對象,可以把他理解為主設備,通過他,可以去掃描和鏈接外設
CBCentralManager *manager;
//用于保存被發現設備
NSMutableArray *discoverPeripherals;
}
一些代理的信息
/*
設置主設備的委托,CBCentralManagerDelegate
必須實現的:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;//主設備狀態改變的委托,在初始化CBCentralManager的適合會打開設備,只有當設備正確打開后才能使用
其他選擇實現的委托中比較重要的:
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外設的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral
*/
- 掃描外設(discover)
掃描外設的方法我們放在centralManagerDidUpdateState,只有設備成功打開,才能開始掃描,否則會報錯。
-(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");
//開始掃描周圍的外設
/*
第一個參數nil就是掃描周圍所有的外設,掃描到外設后會進入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
*/
[central scanForPeripheralsWithServices:nil options:nil];
break;
default:
break;
}
}
掃描到設備后會進入下面方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"當掃描到設備:%@",peripheral.name);
//接下來可以連接設備
}
3.連接設備(connect)
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"當掃描到設備:%@",peripheral.name);
//接下連接我們的測試設備,如果你沒有設備,可以下載一個app叫lightbule的app去模擬一個設備
//這里自己去設置下連接規則,我設置的是P開頭的設備
// if ([peripheral.name hasPrefix:@"P"]){
/*
一個主設備最多能連7個外設,每個外設最多只能給一個主設備連接,連接成功,失敗,斷開會進入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設成功的委托
- (void)centra`lManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設的委托
*/
//找到的設備必須持有它,否則CBCentralManager中也不會保存peripheral,那么CBPeripheralDelegate中的方法也不會被調用!!
[discoverPeripherals addObject:peripheral];
[central connectPeripheral:peripheral options: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]);
}
//連接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>連接到名稱為(%@)的設備-成功",peripheral.name);
//設置的peripheral委托CBPeripheralDelegate
//@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
[peripheral setDelegate:self];
//掃描外設Services,成功后會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
[peripheral discoverServices:nil];
}
**
有一個點非常容易出錯,大家請注意,在didDiscoverPeripheral這個代理中有一行,
/找到的設備必須持有它,否則CBCentralManager中也不會保存peripheral,那么CBPeripheralDelegate中的方法也不會被調用!!
[discoverPeripherals addObject:peripheral];
請特別注意,如果不保存,會影響到后面的方法執行,這個地方最容易出錯,大家也可以看下,在xcode中的說明,重點看@discussion中的內容,里面特別指出了重要的retained對象。
4.掃描外設中的服務和特征(discover)
設備連接成功后,就可以掃描設備中的服務了,同樣是通過代理的形式,掃描到結果后會進入委托方法。但這個委托已經不再是主設備的委托(CBCentralManagerDelegate),而是外設的委托(CBPeripheralDelegate),這個委托包含了主設備與外設交互的許多回調方法,包括獲取services,獲取characteristics,獲取characteristics的值,獲取characteristics的Descriptor,和Descriptor的值,寫數據,讀rssi,用通知的方式訂閱數據等等。
4.1獲取外設的services
//掃描到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(@"%@",service.UUID);
//掃描每個service的Characteristics,掃描到后會進入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
[peripheral discoverCharacteristics:nil forService:service];
}
}
4.2獲取外設的Chatacteristics,獲取Chatacteristics的值,獲取Chatacteristics的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的值,讀到數據會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
{
[peripheral readValueForCharacteristic:characteristic];
}
}
//搜索Characteristic的Descriptors,讀到數據會進入方法:-(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,具體開發時,會根據外設協議制定的方式去解析數據
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
//這個descriptor都是對于characteristic的描述,一般都是字符串,所以這里我們轉換成字符串去解析
NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
5.把數據寫到Characteristic中
//寫數據
-(void)writeCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic
value:(NSData *)value{
//打印出 characteristic 的權限,可以看到有很多種,這是一個NS_OPTIONS,就是可以同時用于好幾個值,常見的有read,write,notify,indicate,知知道這幾個基本就夠用了,前連個是讀寫權限,后兩個都是通知,兩種不同的通知方式。
/*
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的權限才可以寫
if(characteristic.properties & CBCharacteristicPropertyWrite){
/*
最好一個type參數可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區別是是否會有反饋
*/
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"該字段不可寫!");
}
}
6.訂閱Characteristic的通知
//設置通知
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
//設置通知,數據通知會進入:didUpdateValueForCharacteristic方法
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//取消通知
-(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
7.斷開連接(disconnect)
//停止掃描并斷開連接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止掃描
[centralManager stopScan];
//斷開連接
[centralManager cancelPeripheralConnection:peripheral];
}
8 模擬器藍牙調試,慎用,最好還是用真機去調試
由于在iPhone 4s之后的iOS才支持BLE,新一代的這些iOS設備又都不便宜,在做測試的時候,用iOS模擬器進行調試,可以節約一些開發成本。怎么在iOS模擬器上調試BLE,
蘋果最初給出的說明是,支持BLE的mac機子上可以用模擬器進行調試,并給出了一份技術文檔(傳送門),惡心的是,后來蘋果抽風,又把這份文檔移除,
并且把iOS 7.0的模擬器上對BLE的支持也移除掉了(難道是想讓大家多買設備測試?Apple sucks.)后面,網上搜了一下,解決辦法如下:
1. 買一個CSR藍牙4.0 USB適配器(某寶上大概30塊錢),在機子上插入該物(你懂的)
2. 在Terminal下敲入sudo nvram bluetoothHostControllerSwitchBehavior="never" , 重啟Mac。
3. 用XCode 4.6調試代碼,在iOS 6.1的模擬器上跑程序(用XCode 5.0跑iOS 7.0模擬器會拋異常,原因上面詳訴過了,Apple sucks,你懂的)
如何降低模擬器的IOS版本呢?
XCode->Preferences->Downloads里面有很多simulators你可以下載
選擇個6.1的下載好了