上1篇文章介紹了藍牙的技術知識,這里我們具體說明1下中心模式的利用場景。主裝備(手機去掃描連接外設,發現外設服務和屬性,操作服務和屬性的利用。1般來講,外設(藍牙裝備,比如智能手環之類的東西),會由硬件工程師開發好,并定義好裝備提供的服務,每一個服務對的特點,每一個特點的屬性(只讀,只寫,通知等等),本文例子的業務場景,就是用1手機app去讀寫藍牙裝備。
1. 建立中心角色
2. 掃描外設(discover)
3. 連接外設(connect)
4. 掃描外設中的服務和特點(discover)
- 4.1 獲得外設的services
- 4.2 獲得外設的Characteristics,獲得Characteristics的值,獲得Characteristics的Descriptor和Descriptor的值
5. 與外設做數據交互(explore and interact)
6. 定閱Characteristic的通知
7. 斷開連接(disconnect)
1 xcode
2 開發證書和手機(藍牙程序需要使用使用真機調試,使用摹擬器也能夠調試,但是方法很蛋疼,我會放在最后說)
3 藍牙外設
#import <CoreBluetooth/CoreBluetooth.h> @interface ViewController : UIViewController<CBCentralManagerDelegate> @interface ViewController (){ //系統藍牙裝備管理對象,可以把他理解為主裝備,通過他,可以去掃描和鏈接外設 CBCentralManager *manager; //用于保存被發現裝備 NSMutableArray *peripherals; } - (void)viewDidLoad { [super viewDidLoad]; /* 設置主裝備的拜托,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 error:(NSError *)error;//斷開外設的拜托 */ //初始化并設置拜托和線程隊列,最好1個線程的參數可以為nil,默許會就main線程 manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
-(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"); //開始掃描周圍的外設 /* 第1個參數nil就是掃描周圍所有的外設,掃描到外設后會進入 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; */ [manager scanForPeripheralsWithServices:nil options:nil]; break; default: break; } } //掃描到裝備會進入方法 -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ NSLog(@"當掃描到裝備:%@",peripheral.name); //接下來可以連接裝備 }
//掃描到裝備會進入方法 -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ //接下連接我們的測試裝備,如果你沒有裝備,可以下載1個app叫lightbule的app去摹擬1個裝備 //這里自己去設置下連接規則,我設置的是P開頭的裝備 if ([peripheral.name hasPrefix:@"P"]){ /* 1個主裝備最多能連7個外設,每一個外設最多只能給1個主裝備連接,連接成功,失敗,斷開會進入各自的拜托 - (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;//斷開外設的拜托 */ //找到的裝備必須持有它,否則CBCentralManager中也不會保存peripheral,那末CBPeripheralDelegate中的方法也不會被調用?。? [peripherals addObject:peripheral]; //連接裝備 [manager connectPeripheral:peripheral options:nil]; } } //連接到Peripherals-成功 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@">>>連接到名稱為(%@)的裝備-成功",peripheral.name); } //連接到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]); }
有1點非常容易出錯,大家請注意。在 didDiscoverPeripheral
這個拜托中有這1行
//找到的裝備必須持有它,否則CBCentralManager中也不會保存peripheral,那末CBPeripheralDelegate中的方法也不會被調用??! [peripherals addObject:peripheral];
請特別注意,如果不保存,會影響到后面的方法履行,這個地方很多人出錯,在我的藍牙交換群中每天幾近都會由于這個問題致使沒法連接和對外設后續的操作。
大家也能夠看1下這個拜托在xcode中的說明,重點看@discussion中的內容,里面特別指出了需要retained對象。
/*! * @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. For * a 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;
//連接到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]; } //掃描到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]; } }
//掃描到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的描寫,1般都是字符串,所以這里我們轉換成字符串去解析 NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value); }
//寫數據 -(void)writeCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic value:(NSData *)value{ //打印出 characteristic 的權限,可以看到有很多種,這是1個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){ /* 最好1個type參數可以為CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,區分是是不是會有反饋 */ [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; }else{ NSLog(@"該字段不可寫!"); } }
//設置通知 -(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]; }
//停止掃描并斷開連接 -(void)disconnectPeripheral:(CBCentralManager *)centralManager peripheral:(CBPeripheral *)peripheral{ //停止掃描 [centralManager stopScan]; //斷開連接 [centralManager cancelPeripheralConnection:peripheral]; }
由于在iPhone 4s以后的iOS才支持BLE,新1代的這些iOS裝備又都不便宜,在做測試的時候,用iOS摹擬器進行調試,可以節儉1些開發本錢。怎樣在iOS摹擬器上調試BLE,
蘋果最初給出的說明是,支持BLE的mac機子上可以用摹擬器進行調試,并給出了1份技術文檔(傳送門),惡心的是,后來蘋果抽風,又把這份文檔移除,
并且把iOS 7.0的摹擬器上對BLE的支持也移除掉了(難道是想讓大家多買裝備測試?Apple sucks.)后面,網上搜了1下,解決辦法以下:
1. 買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的下載好了
我博客中大部份示例代碼都上傳到了github,地址是:https://github.com/coolnameismy/demo,點擊跳轉代碼下載地址
本文代碼寄存目錄是BleDemo
上一篇 Andfix學習記錄
下一篇 Manacher 算法模板