iOS開發-藍牙模塊

iOS6開始蘋果推出了CoreBluetooth.framework,這個框架最大的特色就是徹底基於BLE4.0標準而且支持非iOS設備。當前BLE應用至關普遍,再也不僅僅是兩個設備之間的數據傳輸,它還有不少其餘應用市場,例如室內定位、無線支付、智能家居等等,這也使得CoreBluetooth成爲當前最熱門的藍牙技術。CoreBluetooth設計一樣也是相似於客戶端-服務器端的設計,做爲服務器端的設備稱爲外圍設備(Peripheral),做爲客戶端的設備叫作中央設備(Central),CoreBlueTooth整個框架就是基於這兩個概念來設計的。web

外圍設備和中央設備在CoreBluetooth中使用CBPeripheralManager和CBCentralManager表示。
CBPeripheralManager:外圍設備一般用於發佈服務、生成數據、保存數據。外圍設備發佈並廣播服務,告訴周圍的中央設備它的可用服務和特徵。
CBCentralManager:中央設備使用外圍設備的數據。中央設備掃描到外圍設備後會就會試圖創建鏈接,一旦鏈接成功就可使用這些服務和特徵。
外圍設備和中央設備之間交互的橋樑是服務(CBService)和特徵(CBCharacteristic),兩者都有一個惟一的標識UUID(CBUUID類型)來惟一肯定一個服務或者特徵。服務器

一臺iOS設備(注意iPhone4如下設備不支持BLE,另外iOS7.0、8.0模擬器也沒法模擬BLE)既能夠做爲外圍設備又能夠做爲中央設備,可是不能同時便是外圍設備又是中央設備,同時注意創建鏈接的過程不須要用戶手動選擇容許,這一點和前面兩個框架是不一樣的,這主要是由於BLE應用場景再也不侷限於兩臺設備之間資源共享了。
A.外圍設備
建立一個外圍設備一般分爲如下幾個步驟:
建立外圍設備CBPeripheralManager對象並指定代理。
建立特徵CBCharacteristic、服務CBSerivce並添加到外圍設備
外圍設備開始廣播服務(startAdvertisting:)。
和中央設備CBCentral進行交互。
下面是簡單的程序示例,程序有兩個按鈕「啓動」和「更新」,點擊啓動按鈕則建立外圍設備、添加服務和特徵並開始廣播,一旦發現有中央設備鏈接並訂閱了此服務的特徵則經過更新按鈕更新特徵數據,此時已訂閱的中央設備就會收到更新數據。框架

界面設計:
這裏寫圖片描述ide

#import "ViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#define kPeripheralName @"Kenshin Cui's Device" //外圍設備名稱
#define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服務的UUID
#define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特徵的UUID

@interface ViewController ()<CBPeripheralManagerDelegate>

@property (strong,nonatomic) CBPeripheralManager *peripheralManager;//外圍設備管理器

@property (strong,nonatomic) NSMutableArray *centralM;//訂閱此外圍設備特徵的中心設備

@property (strong,nonatomic) CBMutableCharacteristic *characteristicM;//特徵
@property (weak, nonatomic) IBOutlet UITextView *log; //日誌記錄

@end

@implementation ViewController
#pragma mark - 視圖控制器方法
- (void)viewDidLoad {
    [super viewDidLoad];
}

#pragma mark - UI事件
//建立外圍設備
- (IBAction)startClick:(UIBarButtonItem *)sender {
    _peripheralManager=[[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
}
//更新數據
- (IBAction)transferClick:(UIBarButtonItem *)sender {
    [self updateCharacteristicValue];
}

#pragma mark - CBPeripheralManager代理方法
//外圍設備狀態發生變化後調用
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
    switch (peripheral.state) {
        case CBPeripheralManagerStatePoweredOn:
            NSLog(@"BLE已打開.");
            [self writeToLog:@"BLE已打開."];
            //添加服務
            [self setupService];
            break;

        default:
            NSLog(@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備.");
            [self writeToLog:@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備."];
            break;
    }
}
//外圍設備添加服務後調用
-(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
    if (error) {
        NSLog(@"向外圍設備添加服務失敗,錯誤詳情:%@",error.localizedDescription);
        [self writeToLog:[NSString stringWithFormat:@"向外圍設備添加服務失敗,錯誤詳情:%@",error.localizedDescription]];
        return;
    }

    //添加服務後開始廣播
    NSDictionary *dic=@{CBAdvertisementDataLocalNameKey:kPeripheralName};//廣播設置
    [self.peripheralManager startAdvertising:dic];//開始廣播
    NSLog(@"向外圍設備添加了服務並開始廣播...");
    [self writeToLog:@"向外圍設備添加了服務並開始廣播..."];
}
-(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
    if (error) {
        NSLog(@"啓動廣播過程當中發生錯誤,錯誤信息:%@",error.localizedDescription);
        [self writeToLog:[NSString stringWithFormat:@"啓動廣播過程當中發生錯誤,錯誤信息:%@",error.localizedDescription]];
        return;
    }
    NSLog(@"啓動廣播...");
    [self writeToLog:@"啓動廣播..."];
}
//訂閱特徵
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
    NSLog(@"中心設備:%@ 已訂閱特徵:%@.",central,characteristic);
    [self writeToLog:[NSString stringWithFormat:@"中心設備:%@ 已訂閱特徵:%@.",central.identifier.UUIDString,characteristic.UUID]];
    //發現中心設備並存儲
    if (![self.centralM containsObject:central]) {
        [self.centralM addObject:central];
    }
    /*中心設備訂閱成功後外圍設備能夠更新特徵值發送到中心設備,一旦更新特徵值將會觸發中心設備的代理方法: -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error */

// [self updateCharacteristicValue];
}
//取消訂閱特徵
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
    NSLog(@"didUnsubscribeFromCharacteristic");
}
-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(CBATTRequest *)request{
    NSLog(@"didReceiveWriteRequests");
}
-(void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict{
    NSLog(@"willRestoreState");
}
#pragma mark -屬性
-(NSMutableArray *)centralM{
    if (!_centralM) {
        _centralM=[NSMutableArray array];
    }
    return _centralM;
}

#pragma mark - 私有方法
//建立特徵、服務並添加服務到外圍設備
-(void)setupService{
    /*1.建立特徵*/
    //建立特徵的UUID對象
    CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
    //特徵值
// NSString *valueStr=kPeripheralName;
// NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding];
    //建立特徵
    /** 參數 * uuid:特徵標識 * properties:特徵的屬性,例如:可通知、可寫、可讀等 * value:特徵值 * permissions:特徵的權限 */
    CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
    self.characteristicM=characteristicM;
// CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];
// characteristicM.value=value;

    /*建立服務而且設置特徵*/
    //建立服務UUID對象
    CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
    //建立服務
    CBMutableService *serviceM=[[CBMutableService alloc]initWithType:serviceUUID primary:YES];
    //設置服務的特徵
    [serviceM setCharacteristics:@[characteristicM]];


    /*將服務添加到外圍設備*/
    [self.peripheralManager addService:serviceM];
}
//更新特徵值
-(void)updateCharacteristicValue{
    //特徵值
    NSString *valueStr=[NSString stringWithFormat:@"%@ --%@",kPeripheralName,[NSDate   date]];
    NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding];
    //更新特徵值
    [self.peripheralManager updateValue:value forCharacteristic:self.characteristicM onSubscribedCentrals:nil];
    [self writeToLog:[NSString stringWithFormat:@"更新特徵值:%@",valueStr]];
}
/** * 記錄日誌 * * @param info 日誌信息 */
-(void)writeToLog:(NSString *)info{
    self.log.text=[NSString stringWithFormat:@"%@\r\n%@",self.log.text,info];
}
@end

B.中央設備
中央設備的建立通常能夠分爲以下幾個步驟:
建立中央設備管理對象CBCentralManager並指定代理。
掃描外圍設備,通常發現可用外圍設備則鏈接並保存外圍設備。
查找外圍設備服務和特徵,查找到可用特徵則讀取特徵數據。
下面是一個簡單的中央服務器端實現,點擊「啓動」按鈕則開始掃描周圍的外圍設備,一旦發現了可用的外圍設備則創建鏈接並設置外圍設備的代理,以後開始查找其服務和特徵。一旦外圍設備的特徵值作了更新,則能夠在代理方法中讀取更新後的特徵值。svg

界面設計:
這裏寫圖片描述ui

#import "ViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服務的UUID
#define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特徵的UUID

@interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate>

@property (strong,nonatomic) CBCentralManager *centralManager;//中心設備管理器
@property (strong,nonatomic) NSMutableArray *peripherals;//鏈接的外圍設備
@property (weak, nonatomic) IBOutlet UITextView *log;//日誌記錄

@end

@implementation ViewController
#pragma mark - 控制器視圖事件
- (void)viewDidLoad {
    [super viewDidLoad];
}

#pragma mark - UI事件
- (IBAction)startClick:(UIBarButtonItem *)sender {
    //建立中心設備管理器並設置當前控制器視圖爲代理
    _centralManager=[[CBCentralManager alloc]initWithDelegate:self queue:nil];
}

#pragma mark - CBCentralManager代理方法
//中心服務器狀態更新後
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
    switch (central.state) {
        case CBPeripheralManagerStatePoweredOn:
            NSLog(@"BLE已打開.");
            [self writeToLog:@"BLE已打開."];
            //掃描外圍設備
// [central scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
            [central scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
            break;

        default:
            NSLog(@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備.");
            [self writeToLog:@"此設備不支持BLE或未打開藍牙功能,沒法做爲外圍設備."];
            break;
    }
}
/** * 發現外圍設備 * * @param central 中心設備 * @param peripheral 外圍設備 * @param advertisementData 特徵數據 * @param RSSI 信號質量(信號強度) */
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    NSLog(@"發現外圍設備...");
    [self writeToLog:@"發現外圍設備..."];
    //中止掃描
    [self.centralManager stopScan];
    //鏈接外圍設備
    if (peripheral) {
        //添加保存外圍設備,注意若是這裏不保存外圍設備(或者說peripheral沒有一個強引用,沒法到達鏈接成功(或失敗)的代理方法,由於在此方法調用完就會被銷燬
        if(![self.peripherals containsObject:peripheral]){
            [self.peripherals addObject:peripheral];
        }
        NSLog(@"開始鏈接外圍設備...");
        [self writeToLog:@"開始鏈接外圍設備..."];
        [self.centralManager connectPeripheral:peripheral options:nil];
    }

}
//鏈接到外圍設備
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    NSLog(@"鏈接外圍設備成功!");
    [self writeToLog:@"鏈接外圍設備成功!"];
    //設置外圍設備的代理爲當前視圖控制器
    peripheral.delegate=self;
    //外圍設備開始尋找服務
    [peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
}
//鏈接外圍設備失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"鏈接外圍設備失敗!");
    [self writeToLog:@"鏈接外圍設備失敗!"];
}

#pragma mark - CBPeripheral 代理方法
//外圍設備尋找到服務後
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    NSLog(@"已發現可用服務...");
    [self writeToLog:@"已發現可用服務..."];
    if(error){
        NSLog(@"外圍設備尋找服務過程當中發生錯誤,錯誤信息:%@",error.localizedDescription);
        [self writeToLog:[NSString stringWithFormat:@"外圍設備尋找服務過程當中發生錯誤,錯誤信息:%@",error.localizedDescription]];
    }
    //遍歷查找到的服務
    CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
    CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
    for (CBService *service in peripheral.services) {
        if([service.UUID isEqual:serviceUUID]){
            //外圍設備查找指定服務中的特徵
            [peripheral discoverCharacteristics:@[characteristicUUID] forService:service];
        }
    }
}
//外圍設備尋找到特徵後
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    NSLog(@"已發現可用特徵...");
    [self writeToLog:@"已發現可用特徵..."];
    if (error) {
        NSLog(@"外圍設備尋找特徵過程當中發生錯誤,錯誤信息:%@",error.localizedDescription);
        [self writeToLog:[NSString stringWithFormat:@"外圍設備尋找特徵過程當中發生錯誤,錯誤信息:%@",error.localizedDescription]];
    }
    //遍歷服務中的特徵
    CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
    CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
    if ([service.UUID isEqual:serviceUUID]) {
        for (CBCharacteristic *characteristic in service.characteristics) {
            if ([characteristic.UUID isEqual:characteristicUUID]) {
                //情景一:通知
                /*找到特徵後設置外圍設備爲已通知狀態(訂閱特徵): *1.調用此方法會觸發代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error *2.調用此方法會觸發外圍設備的訂閱代理方法 */
                [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                //情景二:讀取
// [peripheral readValueForCharacteristic:characteristic];
// if(characteristic.value){
// NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
// NSLog(@"讀取到特徵值:%@",value);
// }
            }
        }
    }
}
//特徵值被更新後
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    NSLog(@"收到特徵更新通知...");
    [self writeToLog:@"收到特徵更新通知..."];
    if (error) {
        NSLog(@"更新通知狀態時發生錯誤,錯誤信息:%@",error.localizedDescription);
    }
    //給特徵值設置新的值
    CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
    if ([characteristic.UUID isEqual:characteristicUUID]) {
        if (characteristic.isNotifying) {
            if (characteristic.properties==CBCharacteristicPropertyNotify) {
                NSLog(@"已訂閱特徵通知.");
                [self writeToLog:@"已訂閱特徵通知."];
                return;
            }else if (characteristic.properties ==CBCharacteristicPropertyRead) {
                //從外圍設備讀取新值,調用此方法會觸發代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
                [peripheral readValueForCharacteristic:characteristic];
            }

        }else{
            NSLog(@"中止已中止.");
            [self writeToLog:@"中止已中止."];
            //取消鏈接
            [self.centralManager cancelPeripheralConnection:peripheral];
        }
    }
}
//更新特徵值後(調用readValueForCharacteristic:方法或者外圍設備在訂閱後更新特徵值都會調用此代理方法)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error) {
        NSLog(@"更新特徵值時發生錯誤,錯誤信息:%@",error.localizedDescription);
        [self writeToLog:[NSString stringWithFormat:@"更新特徵值時發生錯誤,錯誤信息:%@",error.localizedDescription]];
        return;
    }
    if (characteristic.value) {
        NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        NSLog(@"讀取到特徵值:%@",value);
        [self writeToLog:[NSString stringWithFormat:@"讀取到特徵值:%@",value]];
    }else{
        NSLog(@"未發現特徵值.");
        [self writeToLog:@"未發現特徵值."];
    }
}

#pragma mark - 屬性
-(NSMutableArray *)peripherals{
   if(!_peripherals){
       _peripherals=[NSMutableArray array];
   }
   return _peripherals;
}

#pragma mark - 私有方法
/** * 記錄日誌 * * @param info 日誌信息 */
-(void)writeToLog:(NSString *)info{
    self.log.text=[NSString stringWithFormat:@"%@\r\n%@",self.log.text,info];
}

@end