天天看點

IOS學習之藍牙4.0

IOS學習也一段時間了,該上點幹貨了。前段時間研究了一下IOS藍牙通訊相關的東西,把研究的一個成果給大家分享一下。

一 項目背景

簡單介紹一下做的東西,裝置是一個金融刷卡器,通過藍牙與iphone手機通訊。手機端的app通過發送不同的指令(通過藍牙)控制刷卡器執行一些動作,比如讀磁條卡,讀金融ic卡等。上幾張圖容易了解一些:

IOS學習之藍牙4.0
IOS學習之藍牙4.0
IOS學習之藍牙4.0

看了上面幾張圖,你應該大概了解這是個什麼東東了。

二 IOS 藍牙介紹

藍牙協定本身經曆了從1.0到4.0的更新演變, 最新的4.0以其低功耗著稱,是以一般也叫BLE(Bluetoothlow energy)。

iOS 有兩個架構支援藍牙與外設連接配接。一個是 ExternalAccessory。從ios3.0就開始支援,也是在iphone4s出來之前用的比較多的一種模式,但是它有個不好的地方,External Accessory需要拿到蘋果公司的MFI認證。

另一個架構則是本文要介紹的CoreBluetooth,在iphone4s開始支援,專門用于與BLE裝置通訊(因為它的API都是基于BLE的)。這個不需要MFI,并且現在很多藍牙裝置都支援4.0,是以也是在IOS比較推薦的一種開發方法。

三 CoreBluetooth介紹

CoreBluetooth架構的核心其實是兩個東西,peripheral和central, 可以了解成外設和中心。對應他們分别有一組相關的API和類,如下圖所示:

IOS學習之藍牙4.0

 如果你要程式設計的裝置是central那麼你大部分用到,反之亦然。在我們這個示例中,金融刷卡器是peripheral,我們的iphone手機是central,是以我将大部分使用上圖中左邊部分的類。使用peripheral程式設計的例子也有很多,比如像用一個ipad和一個iphone通訊,ipad可以認為是central,iphone端是peripheral,這種情況下在iphone端就要使用上圖右邊部分的類來開發了。

四 服務和特征

有個概念有必要先說明一下。什麼是服務和特征呢(service and characteristic)?

每個藍牙4.0的裝置都是通過服務和特征來展示自己的,一個裝置必然包含一個或多個服務,每個服務下面又包含若幹個特征。特征是與外界互動的最小機關。比如說,一台藍牙4.0裝置,用特征A來描述自己的出廠資訊,用特征B來與收發資料等。

服務和特征都是用UUID來唯一辨別的,UUID的概念如果不清楚請自行google,國際藍牙組織為一些很典型的裝置(比如測量心跳和血壓的裝置)規定了标準的service UUID(特征的UUID比較多,這裡就不列舉了),如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. #define      BLE_UUID_ALERT_NOTIFICATION_SERVICE   0x1811  
  2.  #define     BLE_UUID_BATTERY_SERVICE   0x180F  
  3.  #define     BLE_UUID_BLOOD_PRESSURE_SERVICE   0x1810  
  4.  #define     BLE_UUID_CURRENT_TIME_SERVICE   0x1805  
  5.  #define     BLE_UUID_CYCLING_SPEED_AND_CADENCE   0x1816  
  6.  #define     BLE_UUID_DEVICE_INFORMATION_SERVICE   0x180A  
  7.  #define     BLE_UUID_GLUCOSE_SERVICE   0x1808  
  8.  #define     BLE_UUID_HEALTH_THERMOMETER_SERVICE   0x1809  
  9.  #define     BLE_UUID_HEART_RATE_SERVICE   0x180D  
  10.  #define     BLE_UUID_HUMAN_INTERFACE_DEVICE_SERVICE   0x1812  
  11.  #define     BLE_UUID_IMMEDIATE_ALERT_SERVICE   0x1802  
  12.  #define     BLE_UUID_LINK_LOSS_SERVICE   0x1803  
  13.  #define     BLE_UUID_NEXT_DST_CHANGE_SERVICE   0x1807  
  14.  #define     BLE_UUID_PHONE_ALERT_STATUS_SERVICE   0x180E  
  15.  #define     BLE_UUID_REFERENCE_TIME_UPDATE_SERVICE   0x1806  
  16.  #define     BLE_UUID_RUNNING_SPEED_AND_CADENCE   0x1814  
  17.  #define     BLE_UUID_SCAN_PARAMETERS_SERVICE   0x1813  
  18.  #define     BLE_UUID_TX_POWER_SERVICE   0x1804  
  19.  #define     BLE_UUID_CGM_SERVICE   0x181A  

當然還有很多裝置并不在這個标準清單裡,比如我用的這個金融刷卡器。藍牙裝置硬體廠商通常都會提供他們的裝置裡面各個服務(service)和特征(characteristics)的功能,比如哪些是用來互動(讀寫),哪些可擷取子產品資訊(隻讀)等。

五 實作細節

作為一個中心要實作完整的通訊,一般要經過這樣幾個步驟:

建立中心角色—掃描外設(discover)—連接配接外設(connect)—掃描外設中的服務和特征(discover)—與外設做資料互動(explore and interact)—斷開連接配接(disconnect)。

1建立中心角色

首先在我自己類的頭檔案中要包含CoreBluetooth的頭檔案,并繼承兩個協定<CBCentralManagerDelegate,CBPeripheralDelegate>,代碼如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. #import <CoreBluetooth/CoreBluetooth.h>  
  2. CBCentralManager *manager;  
  3. manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];  

2掃描外設(discover)

代碼如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. [manager scanForPeripheralsWithServices:nil options:options];  

這個參數應該也是可以指定特定的peripheral的UUID,那麼理論上這個central隻會discover這個特定的裝置,但是我實際測試發現,如果用特定的UUID傳參根本找不到任何裝置,我用的代碼如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. NSArray *uuidArray = [NSArray arrayWithObjects:[CBUUID UUIDWithString:@"1800"],[CBUUID UUIDWithString:@"180A"],  
  2. [CBUUID UUIDWithString:@"1CB2D155-33A0-EC21-6011-CD4B50710777"],[CBUUID UUIDWithString:@"6765D311-DD4C-9C14-74E1-A431BBFD0652"],nil];  
  3. [manager scanForPeripheralsWithServices:uuidArray options:options];  

目前不清楚原因,懷疑和裝置本身在的廣播包有關。

3連接配接外設(connect)

當掃描到4.0的裝置後,系統會通過回調函數告訴我們裝置的資訊,然後我們就可以連接配接相應的裝置,代碼如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI  
  2. {  
  3.     if(![_dicoveredPeripherals containsObject:peripheral])  
  4.         [_dicoveredPeripherals addObject:peripheral];  
  5.     NSLog(@"dicoveredPeripherals:%@", _dicoveredPeripherals);  
  6. }  

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. //連接配接指定的裝置  
  2. -(BOOL)connect:(CBPeripheral *)peripheral  
  3. {  
  4.     NSLog(@"connect start");  
  5.     _testPeripheral = nil;  
  6.     [manager connectPeripheral:peripheral  
  7.                        options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];  
  8.     //開一個定時器監控連接配接逾時的情況  
  9.     connectTimer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:@selector(connectTimeout:) userInfo:peripheral repeats:NO];  
  10.     return (YES);  
  11. }  

4掃描外設中的服務和特征(discover)

同樣的,當連接配接成功後,系統會通過回調函數告訴我們,然後我們就在這個回調裡去掃描裝置下所有的服務和特征,代碼如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral  
  2. {  
  3.     [connectTimer invalidate];//停止時鐘  
  4.     NSLog(@"Did connect to peripheral: %@", peripheral);  
  5.     _testPeripheral = peripheral;  
  6.     [peripheral setDelegate:self];  
  7.     [peripheral discoverServices:nil];  
  8. }  

一個裝置裡的服務和特征往往比較多,大部分情況下我們隻是關心其中幾個,是以一般會在發現服務和特征的回調裡去比對我們關心那些,比如下面的代碼:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error  
  2. {  
  3.     NSLog(@"didDiscoverServices");  
  4.     if (error)  
  5.     {  
  6.         NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);  
  7.         if ([self.delegate respondsToSelector:@selector(DidNotifyFailConnectService:withPeripheral:error:)])  
  8.             [self.delegate DidNotifyFailConnectService:nil withPeripheral:nil error:nil];  
  9.         return;  
  10.     }  
  11.     for (CBService *service in peripheral.services)  
  12.     {  
  13.         if ([service.UUID isEqual:[CBUUID UUIDWithString:UUIDSTR_ISSC_PROPRIETARY_SERVICE]])  
  14.         {  
  15.             NSLog(@"Service found with UUID: %@", service.UUID);  
  16.             [peripheral discoverCharacteristics:nil forService:service];  
  17.             isVPOS3356 = YES;  
  18.             break;  
  19.         }  
  20.     }  
  21. }  

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error   
  2. {  
  3.     if (error)   
  4.     {  
  5.         NSLog(@"Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);  
  6.         if ([self.delegate respondsToSelector:@selector(DidNotifyFailConnectChar:withPeripheral:error:)])  
  7.             [self.delegate DidNotifyFailConnectChar:nil withPeripheral:nil error:nil];  
  8.         return;  
  9.     }  
  10.     for (CBCharacteristic *characteristic in service.characteristics)  
  11.     {  
  12.         if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:UUIDSTR_ISSC_TRANS_TX]])  
  13.         {  
  14.             NSLog(@"Discovered read characteristics:%@ for service: %@", characteristic.UUID, service.UUID);  
  15.             _readCharacteristic = characteristic;//儲存讀的特征  
  16.             if ([self.delegate respondsToSelector:@selector(DidFoundReadChar:)])  
  17.                 [self.delegate DidFoundReadChar:characteristic];  
  18.             break;  
  19.         }  
  20.     }  
  21.     for (CBCharacteristic * characteristic in service.characteristics)  
  22.     {  
  23.         if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:UUIDSTR_ISSC_TRANS_RX]])  
  24.         {  
  25.             NSLog(@"Discovered write characteristics:%@ for service: %@", characteristic.UUID, service.UUID);  
  26.             _writeCharacteristic = characteristic;//儲存寫的特征  
  27.             if ([self.delegate respondsToSelector:@selector(DidFoundWriteChar:)])  
  28.                 [self.delegate DidFoundWriteChar:characteristic];  
  29.             break;  
  30.         }  
  31.     }  
  32.     if ([self.delegate respondsToSelector:@selector(DidFoundCharacteristic:withPeripheral:error:)])  
  33.         [self.delegate DidFoundCharacteristic:nil withPeripheral:nil error:nil];  
  34. }  

相信你應該已經注意到了,回調函數都是以"did"開頭的,這些函數不用你調用,達到條件後系統後自動調用。

5與外設做資料互動(explore and interact)

發送資料很簡單,我們可以封裝一個如下的函數:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. //寫資料  
  2. -(void)writeChar:(NSData *)data  
  3. {  
  4.     [_testPeripheral writeValue:data forCharacteristic:_writeCharacteristic type:CBCharacteristicWriteWithResponse];  
  5. }  

_testPeripheral和_writeCharacteristic是前面我們儲存的裝置對象和可以讀寫的特征。

然後我們可以在外部調用它,比如當然我要觸發刷卡時,先組好資料包,然後調用發送函數:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. -(void)msrRead  
  2. {  
  3.     unsigned char command[512] = {0};  
  4.     unsigned charchar *pTmp;  
  5.     int nSendLen = 0;  
  6.     unsigned char ucCrc[3] = {0};  
  7.     _commandType = COMMAND_MSR_READ;  
  8.     pTmp = command;  
  9.     *pTmp = 0x02;//start  
  10.     pTmp++;  
  11.     *pTmp = 0xc1;//main cmd  
  12.     pTmp++;  
  13.     *pTmp = 0x07;//sub cmd  
  14.     pTmp++;  
  15.     nSendLen = 2;  
  16.     *pTmp = nSendLen/256;  
  17.     pTmp++;  
  18.     *pTmp = nSendLen%256;  
  19.     pTmp++;  
  20.     *pTmp = 0x00;//sub cmd  
  21.     pTmp++;  
  22.     *pTmp = 0x00;//sub cmd  
  23.     pTmp++;  
  24.     Crc16CCITT(command+1,pTmp-command-1,ucCrc);  
  25.     memcpy(pTmp,ucCrc,2);  
  26.     NSData *data = [[NSData alloc] initWithBytes:&command length:9];  
  27.     NSLog(@"send data:%@", data);  
  28.     [g_BLEInstance.recvData setLength:0];  
  29.     [g_BLEInstance writeChar:data];  
  30. }  

資料的讀分為兩種,一種是直接讀(reading directly),另外一種是訂閱(subscribe)。從名字也能基本了解兩者的不同。實際使用中具體用一種要看具體的應用場景以及特征本身的屬性。前一個好了解,特征本身的屬性是指什麼呢?特征有個properties字段(characteristic.properties),它是一個整型值,有如下幾個定義:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. enum {  
  2.      CBCharacteristicPropertyBroadcast = 0x01,  
  3.      CBCharacteristicPropertyRead = 0x02,  
  4.      CBCharacteristicPropertyWriteWithoutResponse = 0x04,  
  5.      CBCharacteristicPropertyWrite = 0x08,  
  6.      CBCharacteristicPropertyNotify = 0x10,  
  7.      CBCharacteristicPropertyIndicate = 0x20,  
  8.      CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,  
  9.      CBCharacteristicPropertyExtendedProperties = 0x80,  
  10.      };  

比如說,你要互動的特征,它的properties的值是0x10,表示你隻能用訂閱的方式來接收資料。我這裡是用訂閱的方式,啟動訂閱的代碼如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. //監聽裝置  
  2. -(void)startSubscribe  
  3. {  
  4.     [_testPeripheral setNotifyValue:YES forCharacteristic:_readCharacteristic];  
  5. }  

當裝置有資料傳回時,同樣是通過一個系統回調通知我,如下所示:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error  
  2. {  
  3.     if (error)   
  4.     {  
  5.         NSLog(@"Error updating value for characteristic %@ error: %@", characteristic.UUID, [error localizedDescription]);  
  6.         if ([_mainMenuDelegate respondsToSelector:@selector(DidNotifyReadError:)])  
  7.             [_mainMenuDelegate DidNotifyReadError:error];  
  8.         return;  
  9.     }  
  10.     [_recvData appendData:characteristic.value];  
  11.     if ([_recvData length] >= 5)//已收到長度  
  12.     {  
  13.         unsigned charchar *buffer = (unsigned charchar *)[_recvData bytes];  
  14.         int nLen = buffer[3]*256 + buffer[4];  
  15.         if ([_recvData length] == (nLen+3+2+2))  
  16.         {  
  17.             //接收完畢,通知代理做事  
  18.             if ([_mainMenuDelegate respondsToSelector:@selector(DidNotifyReadData)])  
  19.                 [_mainMenuDelegate DidNotifyReadData];  
  20.         }  
  21.     }  
  22. }  

6 斷開連接配接(disconnect)

這個比較簡單,隻需要一個API就行了,代碼如下:

[objc]  view plain copy

IOS學習之藍牙4.0
IOS學習之藍牙4.0
  1. //主動斷開裝置  
  2. -(void)disConnect  
  3. {  
  4.     if (_testPeripheral != nil)  
  5.     {  
  6.         NSLog(@"disConnect start");  
  7.         [manager cancelPeripheralConnection:_testPeripheral];  
  8.     }  
  9. }  

六 成果展示

上幾張效果圖,UI沒怎麼修飾,主要關注功能,實作了讀取磁道資訊,與金融ic卡進行APDU互動等功能。

IOS學習之藍牙4.0
IOS學習之藍牙4.0
IOS學習之藍牙4.0
IOS學習之藍牙4.0
IOS學習之藍牙4.0

來源位址:http://blog.csdn.net/pony_maggie/article/details/26740237