iOS開發——高級技術&藍牙服務

藍牙服務javascript

藍牙java

隨着藍牙低功耗技術BLE(Bluetooth Low Energy)的發展,藍牙技術正在一步步成熟,現在的大部分移動設備都配備有藍牙4.0,相比以前的藍牙技術耗電量大大下降。從iOS的發展史也不難看 出蘋果目前對藍牙技術也是愈來愈關注,例如蘋果於2013年9月發佈的iOS7就配備了iBeacon技術,這項技術徹底基於藍牙傳輸。可是衆所周知蘋果 的設備對於權限要求也是比較高的,所以在iOS中並不能像Android同樣隨意使用藍牙進行文件傳輸(除非你已經越獄)。在iOS中進行藍牙傳輸應用開 發經常使用的框架有以下幾種:編程

GameKit.framework:iOS7以前的藍牙通信框架,從iOS7開始過時,可是目前多數應用仍是基於此框架。安全

MultipeerConnectivity.framework:iOS7開始引入的新的藍牙通信開發框架,用於取代GameKit。服務器

CoreBluetooth.framework:功能強大的藍牙開發框架,要求設備必須支持藍牙4.0。網絡

前 兩個框架使用起來比較簡單,可是缺點也比較明顯:僅僅支持iOS設備,傳輸內容僅限於沙盒或者照片庫中用戶選擇的文件,而且第一個框架只能在同一個應用之 間進行傳輸(一個iOS設備安裝應用A,另外一個iOS設備上安裝應用B是沒法傳輸的)。固然CoreBluetooth就擺脫了這些束縛,它再也不侷限於 iOS設備之間進行傳輸,你能夠經過iOS設備向Android、Windows Phone以及其餘安裝有藍牙4.0芯片的智能設備傳輸,所以也是目前智能家居、無線支付等熱門智能設備所推崇的技術。session

GameKit併發

其 實從名稱來看這個框架並非專門爲了支持藍牙傳輸而設計的,它是爲遊戲設計的。而不少遊戲中會用到基於藍牙的點對點信息傳輸,所以這個框架中集成了藍牙傳 輸模塊。前面也說了這個框架自己有不少限制,可是在iOS7以前的不少藍牙傳輸都是基於此框架的,因此有必要對它進行了解。GameKit中的藍牙使用設 計很簡單,並無給開發者留有太多的複雜接口,而多數鏈接細節開發者是不須要關注的。GameKit中提供了兩個關鍵類來操做藍牙鏈接:框架

GKPeerPickerController: 藍牙查找、鏈接用的視圖控制器,一般狀況下應用程序A打開後會調用此控制器的show方法來展現一個藍牙查找的視圖,一旦發現了另外一個一樣在查找藍牙鏈接 的客戶客戶端B就會出如今視圖列表中,此時若是用戶點擊鏈接B,B客戶端就會詢問用戶是否容許A鏈接B,若是容許後A和B之間創建一個藍牙鏈接。ide

GKSession:鏈接會話,主要用於發送和接受傳輸數據。一旦A和B創建鏈接GKPeerPickerController的代理方法會將A、B二者創建的會話(GKSession)對象傳遞給開發人員,開發人員拿到此對象能夠發送和接收數據。

其 實理解了上面兩個類以後,使用起來就比較簡單了,下面就以一個圖片發送程序來演示GameKit中藍牙的使用。此程序一個客戶端運行在模擬器上做爲客戶端 A,另外一個運行在iPhone真機上做爲客戶端B(注意A、B必須運行同一個程序,GameKit藍牙開發是不支持兩個不一樣的應用傳輸數據的)。兩個程序 運行以後均調用GKPeerPickerController來發現周圍藍牙設備,一旦A發現了B以後就開始鏈接B,而後iOS會詢問用戶是否接受鏈接, 一旦接受以後就會調用GKPeerPickerController的-(void)peerPickerController: (GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session代理方法,在此方法中能夠得到鏈接的設備id(peerID)和鏈接會話(session);此時能夠設置會話的數據接收句柄(至關於 一個代理)並保存會話以便發送數據時使用;一旦一端(假設是A)調用會話的sendDataToAllPeers: withDataMode: error:方法發送數據,此時另外一端(假設是B)就會調用句柄的- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context方法,在此方法能夠得到發送數據並處理。下面是程序代碼:

 
複製代碼
 1 //  2 // ViewController.m  3 // GameKit  4 //  5 // Created by Kenshin Cui on 14/04/05.  6 // Copyright (c) 2014年 cmjstudio. All rights reserved.  7 //  8 #import "ViewController.h"  9 #import 10 @interface ViewController () 11 @property (weak, nonatomic) IBOutlet UIImageView *imageView;//照片顯示視圖 12 @property (strong,nonatomic) GKSession *session;//藍牙鏈接會話 13 @end 14 @implementation ViewController 15 #pragma mark - 控制器視圖方法 16 - (void)viewDidLoad { 17  [super viewDidLoad]; 18 19 GKPeerPickerController *pearPickerController=[[GKPeerPickerController alloc]init]; 20 pearPickerController.delegate=self; 21 22  [pearPickerController show]; 23 } 24 #pragma mark - UI事件 25 - (IBAction)selectClick:(UIBarButtonItem *)sender { 26 UIImagePickerController *imagePickerController=[[UIImagePickerController alloc]init]; 27 imagePickerController.delegate=self; 28 29  [self presentViewController:imagePickerController animated:YES completion:nil]; 30 } 31 - (IBAction)sendClick:(UIBarButtonItem *)sender { 32 NSData *data=UIImagePNGRepresentation(self.imageView.image); 33 NSError *error=nil; 34 [self.session sendDataToAllPeers:data withDataMode:GKSendDataReliable error:&error]; 35 if (error) { 36 NSLog(@"發送圖片過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); 37  } 38 } 39 #pragma mark - GKPeerPickerController代理方法 40 /** 41  * 鏈接到某個設備 42  * 43  * @param picker 藍牙點對點鏈接控制器 44  * @param peerID 鏈接設備藍牙傳輸ID 45  * @param session 鏈接會話 46 */ 47 -(void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session{ 48 self.session=session; 49 NSLog(@"已鏈接客戶端設備:%@.",peerID); 50 //設置數據接收處理句柄,至關於代理,一旦數據接收完成調用它的-receiveData:fromPeer:inSession:context:方法處理數據 51  [self.session setDataReceiveHandler:self withContext:nil]; 52 53 [picker dismiss];//一旦鏈接成功關閉窗口 54 } 55 #pragma mark - 藍牙數據接收方法 56 - (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context{ 57 UIImage *image=[UIImage imageWithData:data]; 58 self.imageView.image=image; 59  UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); 60 NSLog(@"數據發送成功!"); 61 } 62 #pragma mark - UIImagePickerController代理方法 63 -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ 64 self.imageView.image=[info objectForKey:UIImagePickerControllerOriginalImage]; 65  [self dismissViewControllerAnimated:YES completion:nil]; 66 } 67 -(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{ 68  [self dismissViewControllerAnimated:YES completion:nil]; 69 } 70 @end
複製代碼

 

運行效果(左側是真機,右側是模擬器,程序演示了兩個客戶端互發圖片的場景:首先是模擬器發送圖片給真機,而後真機發送圖片給模擬器):

gif.gif

MultipeerConnectivity 前 面已經說了GameKit相關的藍牙操做類從iOS7已經所有過時,蘋果官方推薦使用MultipeerConnectivity代替。可是應該了 解,MultipeerConnectivity.framework並不只僅支持藍牙鏈接,準確的說它是一種支持Wi-Fi網絡、P2P  Wi-Fi已經藍牙我的局域網的通訊框架,它屏蔽了具體的鏈接技術,讓開發人員有統一的接口編程方法。經過MultipeerConnectivity連 接的節點之間能夠安全的傳遞信息、流或者其餘文件資源而沒必要經過網絡服務。此外使用MultipeerConnectivity進行近場通訊也再也不侷限於 同一個應用之間傳輸,而是能夠在不一樣的應用之間進行數據傳輸(固然若是有必要的話你仍然能夠選擇在一個應用程序之間傳輸)。

要了解MultipeerConnectivity的使用必需要清楚一個概念:廣播(Advertisting)和發現(Disconvering),這很相似 於一種Client-Server模式。假設有兩臺設備A、B,B做爲廣播去發送自身服務,A做爲發現的客戶端。一旦A發現了B就試圖創建鏈接,通過B同 意兩者創建鏈接就能夠相互發送數據。在使用GameKit框架時,A和B既做爲廣播又做爲發現,固然這種狀況在MultipeerConnectivity中也很常見。

  • A.廣播

無 論是做爲服務器端去廣播仍是做爲客戶端去發現廣播服務,那麼兩個(或更多)不一樣的設備之間必需要有區分,一般狀況下使用MCPeerID對象來區分一臺設 備,在這個設備中能夠指定顯示給對方查看的名稱(display  name)。另外無論是哪一方,還必須創建一個會話MCSession用於發送和接受數據。一般狀況下會在會話的-(void)session: (MCSession *)session peer:(MCPeerID *)peerID  didChangeState:(MCSessionState)state代理方法中跟蹤會話狀態(已鏈接、正在鏈接、未鏈接);在會話的-(void)session:(MCSession *)session didReceiveData:(NSData *)data  fromPeer:(MCPeerID *)peerID代理方法中接收數據;同時還會調用會話的-(void)sendData:  toPeers:withMode: error:方法去發送數據。

廣播做爲一個服務器去發佈自身服務,供周邊設備發現鏈接。在MultipeerConnectivity中使用MCAdvertiserAssistant來表示一個廣播,一般建立廣播時指定一個會話MCSession對象將廣播服務和會話關聯起來。一旦調用廣播的start方法周邊的設備就能夠發現該廣播並能夠鏈接到此服務。在MCSession的 代理方法中能夠隨時更新鏈接狀態,一旦創建了鏈接以後就能夠經過MCSession的connectedPeers得到已經鏈接的設備。

  • B.發現

前 面已經說過做爲發現的客戶端一樣須要一個MCPeerID來標誌一個客戶端,同時會擁有一個MCSession來監聽鏈接狀態併發送、接受數據。除此之 外,要發現廣播服務,客戶端就必需要隨時查找服務來鏈接,在MultipeerConnectivity中提供了一個控制器MCBrowserViewController來展現可鏈接和已鏈接的設備(這相似於GameKit中的GKPeerPickerController),固然若是想要本身定製一個界面來展現設備鏈接的狀況你能夠選擇本身開發一套UI界面。一旦經過MCBroserViewController選擇一個節點去鏈接,那麼做爲廣播的節點就會收到通知,詢問用戶是否容許鏈接。因爲初始化MCBrowserViewController的過程已經指定了會話MCSession,因此鏈接過程當中會隨時更新會話狀態,一旦創建了鏈接,就能夠通 過會話的connected屬性得到已鏈接設備而且可使用會話發送、接受數據。

下面用兩個不一樣的應用程序來演示使用MultipeerConnectivity的使用過程,其中一個應用運行在模擬器中做爲廣播節點,另外一個運行在iPhone真機上做爲發現節點,而且實現兩個節點的圖片互傳。

首先看一下做爲廣播節點的程序:

界面:

130912133086626.png

點擊「開始廣播」來發布服務,一旦有節點鏈接此服務就可使用「選擇照片」來從照片庫中選取一張圖片併發送到全部已鏈接節點。

程序:

複製代碼
 1 //  2 // ViewController.m  3 // MultipeerConnectivity_Advertiser  4 //  5 // Created by Kenshin Cui on 14/04/05.  6 // Copyright (c) 2015年 cmjstudio. All rights reserved.  7 //  8 #import "ViewController.h"  9 #import 10 @interface ViewController () 11 @property (strong,nonatomic) MCSession *session; 12 @property (strong,nonatomic) MCAdvertiserAssistant *advertiserAssistant; 13 @property (strong,nonatomic) UIImagePickerController *imagePickerController; 14 @property (weak, nonatomic) IBOutlet UIImageView *photo; 15 @end 16 @implementation ViewController 17 #pragma mark - 控制器視圖事件 18 - (void)viewDidLoad { 19  [super viewDidLoad]; 20 //建立節點,displayName是用於提供給周邊設備查看和區分此服務的 21 MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@"KenshinCui_Advertiser"]; 22 _session=[[MCSession alloc]initWithPeer:peerID]; 23 _session.delegate=self; 24 //建立廣播 25 _advertiserAssistant=[[MCAdvertiserAssistant alloc]initWithServiceType:@"cmj-stream" discoveryInfo:nil session:_session]; 26 _advertiserAssistant.delegate=self; 27 28 } 29 #pragma mark - UI事件 30 - (IBAction)advertiserClick:(UIBarButtonItem *)sender { 31 //開始廣播 32  [self.advertiserAssistant start]; 33 } 34 - (IBAction)selectClick:(UIBarButtonItem *)sender { 35 _imagePickerController=[[UIImagePickerController alloc]init]; 36 _imagePickerController.delegate=self; 37  [self presentViewController:_imagePickerController animated:YES completion:nil]; 38 } 39 #pragma mark - MCSession代理方法 40 -(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{ 41 NSLog(@"didChangeState"); 42 switch (state) { 43 case MCSessionStateConnected: 44 NSLog(@"鏈接成功."); 45 break; 46 case MCSessionStateConnecting: 47 NSLog(@"正在鏈接..."); 48 break; 49 default: 50 NSLog(@"鏈接失敗."); 51 break; 52  } 53 } 54 //接收數據 55 -(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{ 56 NSLog(@"開始接收數據..."); 57 UIImage *image=[UIImage imageWithData:data]; 58  [self.photo setImage:image]; 59 //保存到相冊 60  UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); 61 62 } 63 #pragma mark - MCAdvertiserAssistant代理方法 64 #pragma mark - UIImagePickerController代理方法 65 -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ 66 UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage]; 67  [self.photo setImage:image]; 68 //發送數據給全部已鏈接設備 69 NSError *error=nil; 70 [self.session sendData:UIImagePNGRepresentation(image) toPeers:[self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error]; 71 NSLog(@"開始發送數據..."); 72 if (error) { 73 NSLog(@"發送數據過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); 74  } 75  [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; 76 } 77 -(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{ 78  [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; 79 } 80 @end
複製代碼

 

 

再看一下做爲發現節點的程序:

界面:

MultipeerConnectivity_Discover

點擊「查找設備」瀏覽可用服務,點擊服務創建鏈接;一旦創建了鏈接以後就能夠點擊「選擇照片」會從照片庫中選擇一張圖片併發送給已鏈接的節點。

程序:

複製代碼
 1 //  2 // ViewController.m  3 // MultipeerConnectivity  4 //  5 // Created by Kenshin Cui on 14/04/05.  6 // Copyright (c) 2015年 cmjstudio. All rights reserved.  7 //  8 #import "ViewController.h"  9 #import 10 @interface ViewController () 11 @property (strong,nonatomic) MCSession *session; 12 @property (strong,nonatomic) MCBrowserViewController *browserController; 13 @property (strong,nonatomic) UIImagePickerController *imagePickerController; 14 @property (weak, nonatomic) IBOutlet UIImageView *photo; 15 @end 16 @implementation ViewController 17 #pragma mark - 控制器視圖事件 18 - (void)viewDidLoad { 19  [super viewDidLoad]; 20 //建立節點 21 MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@"KenshinCui"]; 22 //建立會話 23 _session=[[MCSession alloc]initWithPeer:peerID]; 24 _session.delegate=self; 25 26 27 } 28 #pragma mark- UI事件 29 - (IBAction)browserClick:(UIBarButtonItem *)sender { 30 _browserController=[[MCBrowserViewController alloc]initWithServiceType:@"cmj-stream" session:self.session]; 31 _browserController.delegate=self; 32 33  [self presentViewController:_browserController animated:YES completion:nil]; 34 } 35 - (IBAction)selectClick:(UIBarButtonItem *)sender { 36 _imagePickerController=[[UIImagePickerController alloc]init]; 37 _imagePickerController.delegate=self; 38  [self presentViewController:_imagePickerController animated:YES completion:nil]; 39 } 40 #pragma mark - MCBrowserViewController代理方法 41 -(void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController{ 42 NSLog(@"已選擇"); 43  [self.browserController dismissViewControllerAnimated:YES completion:nil]; 44 } 45 -(void)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController{ 46 NSLog(@"取消瀏覽."); 47  [self.browserController dismissViewControllerAnimated:YES completion:nil]; 48 } 49 #pragma mark - MCSession代理方法 50 -(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{ 51 NSLog(@"didChangeState"); 52 switch (state) { 53 case MCSessionStateConnected: 54 NSLog(@"鏈接成功."); 55  [self.browserController dismissViewControllerAnimated:YES completion:nil]; 56 break; 57 case MCSessionStateConnecting: 58 NSLog(@"正在鏈接..."); 59 break; 60 default: 61 NSLog(@"鏈接失敗."); 62 break; 63  } 64 } 65 //接收數據 66 -(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{ 67 NSLog(@"開始接收數據..."); 68 UIImage *image=[UIImage imageWithData:data]; 69  [self.photo setImage:image]; 70 //保存到相冊 71  UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); 72 73 } 74 #pragma mark - UIImagePickerController代理方法 75 -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ 76 UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage]; 77  [self.photo setImage:image]; 78 //發送數據給全部已鏈接設備 79 NSError *error=nil; 80 [self.session sendData:UIImagePNGRepresentation(image) toPeers:[self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error]; 81 NSLog(@"開始發送數據..."); 82 if (error) { 83 NSLog(@"發送數據過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); 84  } 85  [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; 86 } 87 -(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{ 88  [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; 89 } 90 @end
複製代碼

 

 

在 兩個程序中不管是MCBrowserViewController仍是MCAdvertiserAssistant在初始化的時候都指定了一個服務類型「cmj-photo」,這是惟一標識一個服務類型的標記,能夠按照官方的要求命名,應該儘量表達服務的做用。須要特別指出的是,若是廣播命名爲「cmj-photo」那麼發現節點只有在MCBrowserViewController中指定爲「cmj-photo」才能發現此服務。

運行效果:

gif2.gif

CoreBluetooth

無 論是GameKit仍是MultipeerConnectivity,都只能在iOS設備之間進行數據傳輸,這就大大下降了藍牙的使用範圍,因而從iOS6開始蘋果推出了CoreBluetooth.framework,這個框架最大的特色就是徹底基於BLE4.0標準而且支持非iOS設備。當前BLE應用至關普遍,再也不僅僅是兩個設備之間的數據傳輸,它還有不少其餘應用市場,例如室內定位、無線支付、智能家居等等,這也使得CoreBluetooth成爲當前最熱門的藍牙技術。

CoreBluetooth設計一樣也是相似於客戶端-服務器端的設計,做爲服務器端的設備稱爲外圍設備(Peripheral),做爲客戶端的設備叫作中央設備(Central),CoreBlueTooth整個框架就是基於這兩個概念來設計的。

130912175421867.png

外圍設備和中央設備在CoreBluetooth中使用CBPeripheralManager和CBCentralManager表示。

CBPeripheralManager:外圍設備一般用於發佈服務、生成數據、保存數據。外圍設備發佈並廣播服務,告訴周圍的中央設備它的可用服務和特徵。

CBCentralManager:中央設備使用外圍設備的數據。中央設備掃描到外圍設備後會就會試圖創建鏈接,一旦鏈接成功就可使用這些服務和特徵。

外圍設備和中央設備之間交互的橋樑是服務(CBService)和特徵(CBCharacteristic),兩者都有一個惟一的標識UUID(CBUUID類型)來惟一肯定一個服務或者特徵,每一個服務能夠擁有多個特徵,下面是他們之間的關係:

130912203705636.png

一 臺iOS設備(注意iPhone4如下設備不支持BLE,另外iOS7.0、8.0模擬器也沒法模擬BLE)既能夠做爲外圍設備又能夠做爲中央設備,可是 不能同時便是外圍設備又是中央設備,同時注意創建鏈接的過程不須要用戶手動選擇容許,這一點和前面兩個框架是不一樣的,這主要是由於BLE應用場景再也不侷限 於兩臺設備之間資源共享了。

  • A.外圍設備

建立一個外圍設備一般分爲如下幾個步驟:

  1. 建立外圍設備CBPeripheralManager對象並指定代理。

  2. 建立特徵CBCharacteristic、服務CBSerivce並添加到外圍設備

  3. 外圍設備開始廣播服務(startAdvertisting:)。

  4. 和中央設備CBCentral進行交互。

下面是簡單的程序示例,程序有兩個按鈕「啓動」和「更新」,點擊啓動按鈕則建立外圍設備、添加服務和特徵並開始廣播,一旦發現有中央設備鏈接並訂閱了此服務的特徵則經過更新按鈕更新特徵數據,此時已訂閱的中央設備就會收到更新數據。

界面設計:

130912225897991.png

程序設計:

複製代碼
  1 //  2 // ViewController.m  3 // PeripheralApp  4 //  5 // Created by Kenshin Cui on 14/04/05.  6 // Copyright (c) 2014年 cmjstudio. All rights reserved.  7 // 外圍設備(周邊設備)  8 #import "ViewController.h"  9 #import  10 #define kPeripheralName @"Kenshin Cui's Device" //外圍設備名稱  11 #define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服務的UUID  12 #define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特徵的UUID  13 @interface ViewController ()  14 @property (strong,nonatomic) CBPeripheralManager *peripheralManager;//外圍設備管理器  15 @property (strong,nonatomic) NSMutableArray *centralM;//訂閱此外圍設備特徵的中心設備  16 @property (strong,nonatomic) CBMutableCharacteristic *characteristicM;//特徵  17 @property (weak, nonatomic) IBOutlet UITextView *log; //日誌記錄  18 @end  19 @implementation ViewController  20 #pragma mark - 視圖控制器方法  21 - (void)viewDidLoad {  22  [super viewDidLoad];  23 }  24 #pragma mark - UI事件  25 //建立外圍設備  26 - (IBAction)startClick:(UIBarButtonItem *)sender {  27 _peripheralManager=[[CBPeripheralManager alloc]initWithDelegate:self queue:nil];  28 }  29 //更新數據  30 - (IBAction)transferClick:(UIBarButtonItem *)sender {  31  [self updateCharacteristicValue];  32 }  33 #pragma mark - CBPeripheralManager代理方法  34 //外圍設備狀態發生變化後調用  35 -(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{  36 switch (peripheral.state) {  37 case CBPeripheralManagerStatePoweredOn:  38 NSLog(@"BLE已打開.");  39 [self writeToLog:@"BLE已打開."];  40 //添加服務  41  [self setupService];  42 break;  43  44 default:  45 NSLog(@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備.");  46 [self writeToLog:@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備."];  47 break;  48  }  49 }  50 //外圍設備添加服務後調用  51 -(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{  52 if (error) {  53 NSLog(@"向外圍設備添加服務失敗,錯誤詳情:%@",error.localizedDescription);  54 [self writeToLog:[NSString stringWithFormat:@"向外圍設備添加服務失敗,錯誤詳情:%@",error.localizedDescription]];  55 return;  56  }  57  58 //添加服務後開始廣播  59 NSDictionary *dic=@{CBAdvertisementDataLocalNameKey:kPeripheralName};//廣播設置  60 [self.peripheralManager startAdvertising:dic];//開始廣播  61 NSLog(@"向外圍設備添加了服務並開始廣播...");  62 [self writeToLog:@"向外圍設備添加了服務並開始廣播..."];  63 }  64 -(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{  65 if (error) {  66 NSLog(@"啓動廣播過程當中發生錯誤,錯誤信息:%@",error.localizedDescription);  67 [self writeToLog:[NSString stringWithFormat:@"啓動廣播過程當中發生錯誤,錯誤信息:%@",error.localizedDescription]];  68 return;  69  }  70 NSLog(@"啓動廣播...");  71 [self writeToLog:@"啓動廣播..."];  72 }  73 //訂閱特徵  74 -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{  75 NSLog(@"中心設備:%@ 已訂閱特徵:%@.",central,characteristic);  76 [self writeToLog:[NSString stringWithFormat:@"中心設備:%@ 已訂閱特徵:%@.",central.identifier.UUIDString,characteristic.UUID]];  77 //發現中心設備並存儲  78 if (![self.centralM containsObject:central]) {  79  [self.centralM addObject:central];  80  }  81 /*中心設備訂閱成功後外圍設備能夠更新特徵值發送到中心設備,一旦更新特徵值將會觸發中心設備的代理方法:  82  -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error  83 */  84  85 // [self updateCharacteristicValue];  86 }  87 //取消訂閱特徵  88 -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{  89 NSLog(@"didUnsubscribeFromCharacteristic");  90 }  91 -(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(CBATTRequest *)request{  92 NSLog(@"didReceiveWriteRequests");  93 }  94 -(void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict{  95 NSLog(@"willRestoreState");  96 }  97 #pragma mark -屬性  98 -(NSMutableArray *)centralM{  99 if (!_centralM) { 100 _centralM=[NSMutableArray array]; 101  } 102 return _centralM; 103 } 104 #pragma mark - 私有方法 105 //建立特徵、服務並添加服務到外圍設備 106 -(void)setupService{ 107 /*1.建立特徵*/ 108 //建立特徵的UUID對象 109 CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID]; 110 //特徵值 111 // NSString *valueStr=kPeripheralName; 112 // NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding]; 113 //建立特徵 114 /** 參數 115  * uuid:特徵標識 116  * properties:特徵的屬性,例如:可通知、可寫、可讀等 117  * value:特徵值 118  * permissions:特徵的權限 119 */ 120 CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable]; 121 self.characteristicM=characteristicM; 122 // CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable]; 123 // characteristicM.value=value; 124 /*建立服務而且設置特徵*/ 125 //建立服務UUID對象 126 CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID]; 127 //建立服務 128 CBMutableService *serviceM=[[CBMutableService alloc]initWithType:serviceUUID primary:YES]; 129 //設置服務的特徵 130  [serviceM setCharacteristics:@[characteristicM]]; 131 132 133 /*將服務添加到外圍設備*/ 134  [self.peripheralManager addService:serviceM]; 135 } 136 //更新特徵值 137 -(void)updateCharacteristicValue{ 138 //特徵值 139 NSString *valueStr=[NSString stringWithFormat:@"%@ --%@",kPeripheralName,[NSDate date]]; 140 NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding]; 141 //更新特徵值 142  [self.peripheralManager updateValue:value forCharacteristic:self.characteristicM onSubscribedCentrals:nil]; 143 [self writeToLog:[NSString stringWithFormat:@"更新特徵值:%@",valueStr]]; 144 } 145 /** 146  * 記錄日誌 147  * 148  * @param info 日誌信息 149 */ 150 -(void)writeToLog:(NSString *)info{ 151 self.log.text=[NSString stringWithFormat:@"%@\r\n%@",self.log.text,info]; 152 } 153 @end
複製代碼

 

 

上面程序運行的流程以下(圖中藍色表明外圍設備操做,綠色部分表示中央設備操做):

130912243391191.jpg

  • B.中央設備

中央設備的建立通常能夠分爲以下幾個步驟:

  1. 建立中央設備管理對象CBCentralManager並指定代理。

  2. 掃描外圍設備,通常發現可用外圍設備則鏈接並保存外圍設備。

  3. 查找外圍設備服務和特徵,查找到可用特徵則讀取特徵數據。

下面是一個簡單的中央服務器端實現,點擊「啓動」按鈕則開始掃描周圍的外圍設備,一旦發現了可用的外圍設備則創建鏈接並設置外圍設備的代理,以後開始查找其服務和特徵。一旦外圍設備的特徵值作了更新,則能夠在代理方法中讀取更新後的特徵值。

界面設計:

4.png

程序設計:

複製代碼
  1 //  2 // ViewController.m  3 // CentralApp  4 //  5 // Created by Kenshin Cui on 14/04/05.  6 // Copyright (c) 2014年 cmjstudio. All rights reserved.  7 // 中心設備  8 #import "ViewController.h"  9 #import  10 #define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服務的UUID  11 #define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特徵的UUID  12 @interface ViewController ()  13 @property (strong,nonatomic) CBCentralManager *centralManager;//中心設備管理器  14 @property (strong,nonatomic) NSMutableArray *peripherals;//鏈接的外圍設備  15 @property (weak, nonatomic) IBOutlet UITextView *log;//日誌記錄  16 @end  17 @implementation ViewController  18 #pragma mark - 控制器視圖事件  19 - (void)viewDidLoad {  20  [super viewDidLoad];  21 }  22 #pragma mark - UI事件  23 - (IBAction)startClick:(UIBarButtonItem *)sender {  24 //建立中心設備管理器並設置當前控制器視圖爲代理  25 _centralManager=[[CBCentralManager alloc]initWithDelegate:self queue:nil];  26 }  27 #pragma mark - CBCentralManager代理方法  28 //中心服務器狀態更新後  29 -(void)centralManagerDidUpdateState:(CBCentralManager *)central{  30 switch (central.state) {  31 case CBPeripheralManagerStatePoweredOn:  32 NSLog(@"BLE已打開.");  33 [self writeToLog:@"BLE已打開."];  34 //掃描外圍設備  35 // [central scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];  36  [central scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];  37 break;  38  39 default:  40 NSLog(@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備.");  41 [self writeToLog:@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備."];  42 break;  43  }  44 }  45 /**  46  * 發現外圍設備  47  *  48  * @param central 中心設備  49  * @param peripheral 外圍設備  50  * @param advertisementData 特徵數據  51  * @param RSSI 信號質量(信號強度)  52 */  53 -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{  54 NSLog(@"發現外圍設備...");  55 [self writeToLog:@"發現外圍設備..."];  56 //中止掃描  57  [self.centralManager stopScan];  58 //鏈接外圍設備  59 if (peripheral) {  60 //添加保存外圍設備,注意若是這裏不保存外圍設備(或者說peripheral沒有一個強引用,沒法到達鏈接成功(或失敗)的代理方法,由於在此方法調用完就會被銷燬  61 if(![self.peripherals containsObject:peripheral]){  62  [self.peripherals addObject:peripheral];  63  }  64 NSLog(@"開始鏈接外圍設備...");  65 [self writeToLog:@"開始鏈接外圍設備..."];  66  [self.centralManager connectPeripheral:peripheral options:nil];  67  }  68  69 }  70 //鏈接到外圍設備  71 -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{  72 NSLog(@"鏈接外圍設備成功!");  73 [self writeToLog:@"鏈接外圍設備成功!"];  74 //設置外圍設備的代理爲當前視圖控制器  75 peripheral.delegate=self;  76 //外圍設備開始尋找服務  77  [peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]];  78 }  79 //鏈接外圍設備失敗  80 -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{  81 NSLog(@"鏈接外圍設備失敗!");  82 [self writeToLog:@"鏈接外圍設備失敗!"];  83 }  84 #pragma mark - CBPeripheral 代理方法  85 //外圍設備尋找到服務後  86 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{  87 NSLog(@"已發現可用服務...");  88 [self writeToLog:@"已發現可用服務..."];  89 if(error){  90 NSLog(@"外圍設備尋找服務過程當中發生錯誤,錯誤信息:%@",error.localizedDescription);  91 [self writeToLog:[NSString stringWithFormat:@"外圍設備尋找服務過程當中發生錯誤,錯誤信息:%@",error.localizedDescription]];  92  }  93 //遍歷查找到的服務  94 CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];  95 CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];  96 for (CBService *service in peripheral.services) {  97 if([service.UUID isEqual:serviceUUID]){  98 //外圍設備查找指定服務中的特徵  99  [peripheral discoverCharacteristics:@[characteristicUUID] forService:service]; 100  } 101  } 102 } 103 //外圍設備尋找到特徵後 104 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ 105 NSLog(@"已發現可用特徵..."); 106 [self writeToLog:@"已發現可用特徵..."]; 107 if (error) { 108 NSLog(@"外圍設備尋找特徵過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); 109 [self writeToLog:[NSString stringWithFormat:@"外圍設備尋找特徵過程當中發生錯誤,錯誤信息:%@",error.localizedDescription]]; 110  } 111 //遍歷服務中的特徵 112 CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID]; 113 CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID]; 114 if ([service.UUID isEqual:serviceUUID]) { 115 for (CBCharacteristic *characteristic in service.characteristics) { 116 if ([characteristic.UUID isEqual:characteristicUUID]) { 117 //情景一:通知 118 /*找到特徵後設置外圍設備爲已通知狀態(訂閱特徵): 119  *1.調用此方法會觸發代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 120  *2.調用此方法會觸發外圍設備的訂閱代理方法 121 */ 122  [peripheral setNotifyValue:YES forCharacteristic:characteristic]; 123 //情景二:讀取 124 // [peripheral readValueForCharacteristic:characteristic]; 125 // if(characteristic.value){ 126 // NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]; 127 // NSLog(@"讀取到特徵值:%@",value); 128 // } 129  } 130  } 131  } 132 } 133 //特徵值被更新後 134 -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ 135 NSLog(@"收到特徵更新通知..."); 136 [self writeToLog:@"收到特徵更新通知..."]; 137 if (error) { 138 NSLog(@"更新通知狀態時發生錯誤,錯誤信息:%@",error.localizedDescription); 139  } 140 //給特徵值設置新的值 141 CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID]; 142 if ([characteristic.UUID isEqual:characteristicUUID]) { 143 if (characteristic.isNotifying) { 144 if (characteristic.properties==CBCharacteristicPropertyNotify) { 145 NSLog(@"已訂閱特徵通知."); 146 [self writeToLog:@"已訂閱特徵通知."]; 147 return; 148 }else if (characteristic.properties ==CBCharacteristicPropertyRead) { 149 //從外圍設備讀取新值,調用此方法會觸發代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 150  [peripheral readValueForCharacteristic:characteristic]; 151  } 152 153 }else{ 154 NSLog(@"中止已中止."); 155 [self writeToLog:@"中止已中止."]; 156 //取消鏈接 157  [self.centralManager cancelPeripheralConnection:peripheral]; 158  } 159  } 160 } 161 //更新特徵值後(調用readValueForCharacteristic:方法或者外圍設備在訂閱後更新特徵值都會調用此代理方法) 162 -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ 163 if (error) { 164 NSLog(@"更新特徵值時發生錯誤,錯誤信息:%@",error.localizedDescription); 165 [self writeToLog:[NSString stringWithFormat:@"更新特徵值時發生錯誤,錯誤信息:%@",error.localizedDescription]]; 166 return; 167  } 168 if (characteristic.value) { 169 NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]; 170 NSLog(@"讀取到特徵值:%@",value); 171 [self writeToLog:[NSString stringWithFormat:@"讀取到特徵值:%@",value]]; 172 }else{ 173 NSLog(@"未發現特徵值."); 174 [self writeToLog:@"未發現特徵值."]; 175  } 176 } 177 #pragma mark - 屬性 178 -(NSMutableArray *)peripherals{ 179 if(!_peripherals){ 180 _peripherals=[NSMutableArray array]; 181  } 182 return _peripherals; 183 } 184 #pragma mark - 私有方法 185 /** 186  * 記錄日誌 187  * 188  * @param info 日誌信息 189 */ 190 -(void)writeToLog:(NSString *)info{ 191 self.log.text=[NSString stringWithFormat:@"%@\r\n%@",self.log.text,info]; 192 } 193 @end
複製代碼

 

 

上面程序運行的流程圖以下:

130912279173331.jpg

有 了上面兩個程序就能夠分別運行在兩臺支持BLE的iOS設備上,當兩個應用創建鏈接後,一旦外圍設備更新特徵以後,中央設備就能夠當即獲取到更新後的值。 須要強調的是使用CoreBluetooth開發的應用不只僅能夠和其餘iOS設備進行藍牙通訊,還能夠同其餘第三方遵循BLE規範的設備進行藍牙通信, 這裏就再也不贅述。

相關文章
相關標籤/搜索