轉載請註明本文地址:www.jianshu.com/p/38a4c6451…git
最近公司在作一個iOS藍牙項目,在開發的過程當中簡單整理了一些與之相關的基礎知識,在這裏分享一下。整理包括如下內容:github
一、iOS藍牙開發的關鍵詞 二、藍牙的簡單介紹 三、CoreBluetooth框架 四、實現iOS藍牙外設(Demo) 五、實現iOS藍牙中心設備(Demo)框架
Demo的運行gif圖以下,中心設備能夠從外設讀取數據,也能夠向外設寫入數據。外設也能夠向中心設備發送數據。 PS:須要使用真機測試。學習
iOS的藍牙開發是圍繞着CoreBluetooth框架來實現的。 下面先從iOS藍牙開發的基本概念提及。測試
中心設備:就是用來掃描周圍藍牙硬件的設備,好比經過你手機的藍牙來掃描並鏈接智能手環,這時候你的手機就是中心設備。ui
外設:被掃描的設備。好比當你用手機的藍牙掃描鏈接智能手環的時候,智能手環就是外設。編碼
廣播:就是外設不停的散播藍牙信號,讓中心設備能夠掃描到。atom
服務(services):外設廣播和運行的時候會有服務,能夠理解成一個功能模塊,中心設備能夠讀取服務。外設能夠有多個服務。url
特徵(characteristic):在服務中的一個單位,一個服務能夠有多個特徵,特徵會有一個value,通常讀寫的數據就是這個value。spa
UUID:區分不一樣的服務和特徵,能夠理解爲服務和特徵的身份證。咱們能夠用UUID來挑選須要的服務和特徵。
偷個懶:藍牙百科 藍牙( **Bluetooth® **):是一種短距離無線通訊技術 ,可實現固定設備、移動設備和樓宇我的域網之間的短距離數據交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍牙4.2發佈於2014年12月2日,本文發佈的時候,藍牙的最新版本爲4.2。
如上圖所示,iOS中的藍牙開發框架CoreBluetooth處在藍牙低功耗協議棧的上面,咱們開發的時候只是使用CoreBluetooth這個框架,經過CoreBluetooth能夠輕鬆實現外設或中心設備的開發。
CoreBluetooth能夠分爲兩大模塊,中心設備central,外設peripheral,它們倆各有本身的一套API供咱們使用。
上圖左邊的就是中心設備的開發類,咱們平時是使用CBCentralManager來進行相關操做。
- CBCentralManager: 藍牙中心設備管理類,用來統一調度中心設備的開發
右邊是外設開發相關類,通常是圍繞着CBPeripheralManager來進行編碼。
- CBPeripheralManager: 藍牙外設開發時使用,用來開發藍牙外設的中心管理類。
一、首先導入CoreBluetooth框架,並遵照協議
#import <CoreBluetooth/CoreBluetooth.h>
// 遵照CBPeripheralManagerDelegate協議
@interface ViewController () <CBPeripheralManagerDelegate>
複製代碼
二、建立外設管理對象,用一個屬性來強引用這個對象。而且在建立的時候設置代理,聲明放到哪一個線程。
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
// 建立外設管理器,會回調peripheralManagerDidUpdateState方法
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
複製代碼
三、當建立CBPeripheralManager的時候,會回調判斷藍牙狀態的方法。當藍牙狀態沒問題的時候建立外設的Service(服務)和Characteristics(特徵)。
/* 設備的藍牙狀態 CBManagerStateUnknown = 0, 未知 CBManagerStateResetting, 重置中 CBManagerStateUnsupported, 不支持 CBManagerStateUnauthorized, 未驗證 CBManagerStatePoweredOff, 未啓動 CBManagerStatePoweredOn, 可用 */
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
if (peripheral.state == CBManagerStatePoweredOn) {
// 建立Service(服務)和Characteristics(特徵)
[self setupServiceAndCharacteristics];
// 根據服務的UUID開始廣播
[self.peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:SERVICE_UUID]]}];
}
}
複製代碼
能夠先用宏來作兩個標識字符串,用來建立服務和特徵的UUID。 最終把建立好的特徵放進服務,把服務放入中心管理器。
#define SERVICE_UUID @"CDD1"
#define CHARACTERISTIC_UUID @"CDD2"
/** 建立服務和特徵 */
- (void)setupServiceAndCharacteristics {
// 建立服務
CBUUID *serviceID = [CBUUID UUIDWithString:SERVICE_UUID];
CBMutableService *service = [[CBMutableService alloc] initWithType:serviceID primary:YES];
// 建立服務中的特徵
CBUUID *characteristicID = [CBUUID UUIDWithString:CHARACTERISTIC_UUID];
CBMutableCharacteristic *characteristic = [
[CBMutableCharacteristic alloc]
initWithType:characteristicID
properties:
CBCharacteristicPropertyRead |
CBCharacteristicPropertyWrite |
CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable |
CBAttributePermissionsWriteable
];
// 特徵添加進服務
service.characteristics = @[characteristic];
// 服務加入管理
[self.peripheralManager addService:service];
// 爲了手動給中心設備發送數據
self.characteristic = characteristic;
}
複製代碼
注意CBCharacteristicPropertyNotify這個參數,只有設置了這個參數,在中心設備中才能訂閱這個特徵。 通常開發中能夠設置兩個特徵,一個用來發送數據,一個用來接收中心設備寫過來的數據,咱們這裏爲了方便就只設置了一個特徵。 最後用一個屬性拿到這個特徵,是爲了後面單獨發送數據的時候使用,數據的寫入和讀取最終仍是要經過特徵來完成。
四、當中心設備讀取這個外設的數據的時候會回調這個方法。
/** 中心設備讀取數據的時候回調 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
// 請求中的數據,這裏把文本框中的數據發給中心設備
request.value = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
// 成功響應請求
[peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}
複製代碼
五、當中心設備寫入數據的時候,外設會調用下面這個方法。
/** 中心設備寫入數據的時候回調 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
// 寫入數據的請求
CBATTRequest *request = requests.lastObject;
// 把寫入的數據顯示在文本框中
self.textField.text = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
}
複製代碼
六、還有一個主動給中心設備發送數據的方法。
/** 經過固定的特徵發送數據到中心設備 */
- (IBAction)didClickPost:(id)sender {
BOOL sendSuccess = [self.peripheralManager updateValue:[self.textField.text dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristic onSubscribedCentrals:nil];
if (sendSuccess) {
NSLog(@"數據發送成功");
}else {
NSLog(@"數據發送失敗");
}
}
複製代碼
七、中心設備訂閱成功的時候回調。
/** 訂閱成功回調 */
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"%s",__FUNCTION__);
}
複製代碼
八、中心設備取消訂閱的時候回調。
/** 取消訂閱回調 */
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"%s",__FUNCTION__);
}
複製代碼
以上就是iOS藍牙外設的基本實現流程,固然還有更多的地方能夠進一步處理,這就須要投入更多的時間來學習實驗了。
下面進入iOS藍牙開發的主要部分,中心設備的實現,這也是手機App一般擔任的角色。
一、同外設開發同樣,首先要導入CoreBluetooth框架。
#import <CoreBluetooth/CoreBluetooth.h>
複製代碼
二、遵照的協議與外設開發不一樣,中心設備的開發須要遵循以下兩個協議。
@interface ViewController () <CBCentralManagerDelegate,CBPeripheralDelegate>
複製代碼
三、建立中心管理器並用屬性強引用,建立的時候也會設置代理和選擇線程。
@property (nonatomic, strong) CBCentralManager *centralManager;
// 建立中心設備管理器,會回調centralManagerDidUpdateState
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
複製代碼
四、當建立中心管理對象的時候,會回調以下方法用來判斷中心設備的藍牙狀態。當藍牙狀態沒問題的時候,能夠根據外設服務的UUID來掃描須要的外設。因此天然而然的就想到了要定義與外設UUID相同的宏。
/** 判斷手機藍牙狀態 */
#define SERVICE_UUID @"CDD1"
#define CHARACTERISTIC_UUID @"CDD2"
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
// 藍牙可用,開始掃描外設
if (central.state == CBManagerStatePoweredOn) {
NSLog(@"藍牙可用");
// 根據SERVICE_UUID來掃描外設,若是不設置SERVICE_UUID,則掃描全部藍牙設備
[central scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:SERVICE_UUID]] options:nil];
}
if(central.state==CBCentralManagerStateUnsupported) {
NSLog(@"該設備不支持藍牙");
}
if (central.state==CBCentralManagerStatePoweredOff) {
NSLog(@"藍牙已關閉");
}
}
複製代碼
五、當掃描到外設以後,就會回調下面這個方法,能夠在這個方法中繼續設置篩選條件,例如根據外設名字的前綴來選擇,若是符合條件就進行鏈接。
/** 發現符合要求的外設,回調 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI {
// 對外設對象進行強引用
self.peripheral = peripheral;
// if ([peripheral.name hasPrefix:@"WH"]) {
// // 能夠根據外設名字來過濾外設
// [central connectPeripheral:peripheral options:nil];
// }
// 鏈接外設
[central connectPeripheral:peripheral options:nil];
}
複製代碼
七、當鏈接成功的時候,就會來到下面這個方法。爲了省電,當鏈接上外設以後,就讓中心設備中止掃描,而且別忘記設置鏈接上的外設的代理。在這個方法里根據UUID進行服務的查找。
/** 鏈接成功 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
// 能夠中止掃描
[self.centralManager stopScan];
// 設置代理
peripheral.delegate = self;
// 根據UUID來尋找服務
[peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
NSLog(@"鏈接成功");
}
複製代碼
八、鏈接失敗和斷開鏈接也有各自的回調方法。在斷開鏈接的時候,咱們能夠設置自動重連,根據項目需求來自定義裏面的代碼。
/** 鏈接失敗的回調 */
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"鏈接失敗");
}
/** 斷開鏈接 */
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
NSLog(@"斷開鏈接");
// 斷開鏈接能夠設置從新鏈接
[central connectPeripheral:peripheral options:nil];
}
複製代碼
九、下面開始處理代理方法。 最開始就是發現服務的方法。這個方法裏能夠遍歷服務,找到須要的服務。因爲上面作的外設只有一個服務,因此我這裏直接取服務中的最後一個lastObject就好了。 找到服務以後,連貫的動做繼續根據特徵的UUID尋找服務中的特徵。
/** 發現服務 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
// 遍歷出外設中全部的服務
for (CBService *service in peripheral.services) {
NSLog(@"全部的服務:%@",service);
}
// 這裏僅有一個服務,因此直接獲取
CBService *service = peripheral.services.lastObject;
// 根據UUID尋找服務中的特徵
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service];
}
複製代碼
十、下面這個方法裏作的事情很多。 當發現特徵以後,與服務同樣能夠遍歷特徵,根據外設開發人員給的文檔找出不一樣特徵,作出相應的操做。 個人外設只設置了一個特徵,因此也是直接經過lastObject拿到特徵。 再重複一遍,通常開發中能夠設置兩個特徵,一個用來發送數據,一個用來接收中心設備寫過來的數據。 這裏用一個屬性引用特徵,是爲了後面經過這個特徵向外設寫入數據或發送指令。 readValueForCharacteristic方法是直接讀一次這個特徵上的數據。
/** 發現特徵回調 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
// 遍歷出所須要的特徵
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"全部特徵:%@", characteristic);
// 從外設開發人員那裏拿到不一樣特徵的UUID,不一樣特徵作不一樣事情,好比有讀取數據的特徵,也有寫入數據的特徵
}
// 這裏只獲取一個特徵,寫入數據的時候須要用到這個特徵
self.characteristic = service.characteristics.lastObject;
// 直接讀取這個特徵數據,會調用didUpdateValueForCharacteristic
[peripheral readValueForCharacteristic:self.characteristic];
// 訂閱通知
[peripheral setNotifyValue:YES forCharacteristic:self.characteristic];
}
複製代碼
setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic方法是對這個特徵進行訂閱,訂閱成功以後,就能夠監控外設中這個特徵值得變化了。
十一、當訂閱的狀態發生改變的時候,下面的方法就派上用場了。
/** 訂閱狀態的改變 */
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"訂閱失敗");
NSLog(@"%@",error);
}
if (characteristic.isNotifying) {
NSLog(@"訂閱成功");
} else {
NSLog(@"取消訂閱");
}
}
複製代碼
十二、外設能夠發送數據給中心設備,中心設備也能夠從外設讀取數據,當發生這些事情的時候,就會回調這個方法。經過特種中的value屬性拿到原始數據,而後根據需求解析數據。
/** 接收到數據回調 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
// 拿到外設發送過來的數據
NSData *data = characteristic.value;
self.textField.text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
複製代碼
1三、中心設備能夠向外設寫入數據,也能夠向外設發送請求或指令,當須要進行這些操做的時候該怎麼辦呢。
首先把要寫入的數據轉化爲NSData格式,而後根據上面拿到的寫入數據的特徵,運用方法writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type來進行數據的寫入。
當寫入數據的時候,系統也會回調這個方法peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error 。
/** 寫入數據 */
- (IBAction)didClickPost:(id)sender {
// 用NSData類型來寫入
NSData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
// 根據上面的特徵self.characteristic來寫入數據
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
/** 寫入數據回調 */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
NSLog(@"寫入成功");
}
複製代碼
1四、中心設備如何主動從外設讀取數據呢。
/** 讀取數據 */
- (IBAction)didClickGet:(id)sender {
[self.peripheral readValueForCharacteristic:self.characteristic];
}
複製代碼
中心設備的開發是須要配合外設來進行的,通常會有硬件工程師或嵌入式工程師給出通訊協議,根據協議來對項目的各類需求進行操做。
本文所述的示例代碼在這裏:Demo