iOS 藍芽技術CoreBluetooth 使用手冊
最近很閒,沒什麼事可做,想來不能閒著,所以就想著研究下藍芽相關的技術,所以就嘗試了下,外設和中心裝置都是手機,一個手機做當做虛擬外設,一個作為中心裝置,嘗試連線和通訊一切都還順利。當然也知道在實際開發中會遇到意想不到的問題。過來之人可以一起探討。
文中所提有不足之處 還望指正。先謝過[抱拳]。
IOS藍芽技術—CoreBluetooth
一、iOS藍芽簡述
iOS開發中關於藍芽技術的框架有四種:
- GameKit.framework 多用於遊戲開發,iOS裝置之間的連線。
- MultipeerConnectivity.framework iOS裝置之間傳遞檔案,點對點的連線。
- ExternalAccessory.framework 主要是用於和藍顏2.0(需要蘋果認證)裝置連線通訊的框架。
- CoreBluetooth.framework 主要使用者和藍芽4.0的裝置連線和通訊。藍芽4.0也叫 BLE。
在這裡我們就主要針對藍芽4.0的開發,也就是iOS的BLE開發。
二、CoreBluetooth簡介
1、基本概念
中心裝置(central)。主動發起連線的裝置。
外設(peripheral)。外部裝置,比如耳機,音箱,智慧手環。(當然手機也可以是外部裝置,中興裝置也可能成為外部裝置)。
服務。每個外設都有若干個服務,可以新增和刪除服務。外設可以廣播服務供中心裝置區掃描。每個服務都有一個UUID,用來標識每個服務。
特徵。每個服務都有若干個特徵,裝置之間是通過特徵值來進行資料互動的。這些特徵值有很多屬性(比如:讀,寫,通知)。只有這個特徵設定了相應的屬性才能進行相應的操作。如果沒有讀的屬性,中心裝置數無法讀取到外設的資料的。
特徵的屬性。每個特徵都有它的屬性值。不同的屬性限制了外設對外提供的服務。讀的屬性可以有對外提供資料的能力,寫屬性可以對外提供資料寫入特徵的能力。
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(10_9, 6_0) = 0x100,
CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x200
};
2、CoreBluetooth 框架介紹
CBCentralManager 中心裝置管理,檢測裝置狀態,掃描外部裝置,連結外部裝置等。
CBPeripheralManager 外部裝置管理,檢測外部裝置狀態,新增/移除服務,廣播服務,更新特徵值,響應中心裝置的讀寫等。
CBPeripheral 外設物件 向外設寫/讀資料,訂閱通知,查詢服務,查詢服務特徵等。
CBCentral 中心裝置
CBMutableService 特徵服務
CBMutableCharacteristic 特徵
CBUUID 表示id
CBATTError 狀態碼, 比如響應請求返回狀態碼。
typedef NS_ENUM(NSInteger, CBATTError) {
CBATTErrorSuccess NS_ENUM_AVAILABLE(10_9, 6_0) = 0x00,
CBATTErrorInvalidHandle = 0x01,
CBATTErrorReadNotPermitted = 0x02,
CBATTErrorWriteNotPermitted = 0x03,
CBATTErrorInvalidPdu = 0x04,
CBATTErrorInsufficientAuthentication = 0x05,
CBATTErrorRequestNotSupported = 0x06,
CBATTErrorInvalidOffset = 0x07,
CBATTErrorInsufficientAuthorization = 0x08,
CBATTErrorPrepareQueueFull = 0x09,
CBATTErrorAttributeNotFound = 0x0A,
CBATTErrorAttributeNotLong = 0x0B,
CBATTErrorInsufficientEncryptionKeySize = 0x0C,
CBATTErrorInvalidAttributeValueLength = 0x0D,
CBATTErrorUnlikelyError = 0x0E,
CBATTErrorInsufficientEncryption = 0x0F,
CBATTErrorUnsupportedGroupType = 0x10,
CBATTErrorInsufficientResources = 0x11
};
三、開發模式
1、中心模式
開發步驟:
- 建立中心裝置管理(CBCentralManager)。
- 掃描外設。
- 連結外部裝置。
- 找到外設服務。
- 找到外設服務特徵。
- 通過特徵做資料互動。
- 互動完畢斷開連結。
互動圖如下:
首先建立中心裝置管理
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
//藍芽power沒開啟時alert提示框
[NSNumber numberWithBool:YES],CBCentralManagerOptionShowPowerAlertKey,
//重設centralManager恢復的IdentifierKey
@"IdentifierKey",CBCentralManagerOptionRestoreIdentifierKey,
nil];
self.centralManageer = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:options];
這個時候代理會回撥檢測裝置狀態
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
if(central.state == CBManagerStateUnknown){
NSLog(@"裝置未知");
[central stopScan];
}else if(central.state == CBManagerStateResetting){
}else if(central.state == CBManagerStateUnsupported){
NSLog(@"裝置不支援");
[central stopScan];
}else if(central.state == CBManagerStateUnauthorized){
NSLog(@"裝置未認證");
[central stopScan];
}else if(central.state == CBManagerStatePoweredOff){
NSLog(@"藍芽不可用");
[central stopScan];
}else if(central.state == CBManagerStatePoweredOn){
NSLog(@"可用");
// 開始掃面外設
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
[self.centralManageer scanForPeripheralsWithServices:nil options:options];
}else {
NSLog(@"error");
}
}
掃描到外設的回撥
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
// peripheral 外設物件 (如果掃描到的統一外設,物件也是同一個不是新建的物件)
// advertisementData 外設服務的廣播資料
// RSSI 訊號強度 (可以計算裝置的距離)
}
連結外部裝置
[self.centralManageer connectPeripheral:peripheral options:nil];
連結回撥
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@"連結成功");
// 停止掃描
[self.centralManageer stopScan];
// 設定代理
self.perpheral.delegate = self;
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"連線失敗");
}
查詢外設服務
[peripheral discoverServices:nil];
找到外設服務的回撥
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
for (CBService *service in peripheral.services) {
// 外設服務
}
}
通過找的外設服務再查詢外設服務的特徵
[peripheral discoverCharacteristics:nil forService:service];
查詢到服務特徵的回撥
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
for(CBCharacteristic *character in service.characteristics){
//character 所有的特徵
}
}
讀取特徵服務的資料
// 只有特徵的屬性包含讀 才能讀取到資料
[peripheral readValueForCharacteristic:character];
特徵讀取回調(外設服務特徵值被修改也會回撥到這裡)
// 【訂閱之後】才會收到特徵資料更新的通知
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
NSLog(@"----------characteristic--------:%@",characteristic);
NSString *sting = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]; // 假設是個字串這樣解析,也有可能是其他的資料格式 和一些特殊的資訊表示
NSLog(@"----value:%@",sting);
}
給特徵寫資料
// data 每一包傳輸的資料大小有限制,經過測試最大可以傳輸512個位元組,超過就會寫失敗。
[self.perpheral writeValue:data forCharacteristic:characteristicl type:CBCharacteristicWriteWithResponse];
// CBCharacteristicWriteWithResponse 需要在寫完資料之後得到外部裝置的響應 ,如果沒有響應處理這裡就會寫失敗
寫資料回撥
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if(!error){
NSLog(@"寫資料");
}else{
// 可能寫入資料過大或者是寫入需要外設響應但是外設沒有設定響應,這個時候也會出錯
NSLog(@"寫資料失敗");
}
}
訂閱特徵
// 開啟和關閉訂閱
//只有訂閱開啟了之後 外設特徵跟新才會通知到中心裝置 (中心裝置的回撥才能接收到)
[self.perpheral setNotifyValue:BOOL forCharacteristic:characteristicl];
// 訂閱狀態的回撥
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if(!error){
NSLog(@"訂閱成功");
}else{
NSLog(@"訂閱失敗");
}
}
2、外設模式
開發步驟
- 建立外設管理。
- 建立外設服務並新增服務。
- 建立外設特徵並給服務新增特徵。
- 廣播服務。
- 處理訂閱和讀寫請求。
互動圖如下:
建立外設管理
self.peripheralManage = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil]
// 外設狀態回撥
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
switch (peripheral.state) {
case CBManagerStateUnknown:
break;
case CBManagerStateResetting:
break;
case CBManagerStateUnsupported:
break;
case CBManagerStateUnauthorized:
break;
case CBManagerStatePoweredOff:
break;
case CBManagerStatePoweredOn:
// 可以新增外設服務
break;
default:
break;
}
}
建立外設服務,建立特徵,服務新增特徵
//1、建立服務
CBMutableService *service = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"你的服務id"] primary:YES];
// 建立特徵
CBMutableCharacteristic *readCharactertistic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"特徵ID"] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable]; // CBCharacteristicPropertyRead 這是一個只可以讀取的特徵 CBAttributePermissionsReadable 給中心裝置讀的許可權。
// 還可以建立一個特徵描述
CBMutableDescriptor *descriptor = [[CBMutableDescriptor alloc] initWithType:[CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString] value:@"讀寫特徵"];
[readCharactertistic setDescriptors:@[descriptor]];
// 給服務新增特徵
[service setCharacteristics:@[readCharactertistic]];
給外設新增服務
[self.peripheralManage addService:service];
開始廣播特徵服務
NSDictionary *dic = @{CBAdvertisementDataLocalNameKey:@"virtual"}; // 這個資料可以在中心裝置掃描到外設的時候接收到這個資料
[self.peripheralManage startAdvertising:dic];
// 廣播成功的回撥
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
if(!error){
NSLog(@"廣播成功");
// 這個時候中心裝置就能掃描到外設的服務
}else{
NSLog(@"廣播失敗");
}
}
中心裝置請求處理
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
// 取消訂閱之後才可以再次訂閱
NSLog(@"收到訂閱");
// 這個時候我們修改特徵值中心裝置就會收到特徵值更新的通知回撥
// 這裡的data 6p測試最大是155位元組
[peripheral updateValue:data forCharacteristic:characteristic onSubscribedCentrals:nil];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(nonnull CBCharacteristic *)characteristic{
NSLog(@"----取消訂閱");
// 做一些需要處理的邏輯
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
NSLog(@"didReceiveReadRequest");
// 收到讀資料的請求
// 當中心裝置的
[peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests{
// 收到寫資料的請求 如果寫資料的時候中心裝置需要外設響應的是沒有響應中心裝置寫資料就會失敗
NSLog(@"didReceiveWriteRequests");
CBATTRequest *request = requests[0];// 這裡只處理了第一個....
NSString *sting = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
NSLog(@"----value:%@",sting)
[peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}