轉載請註明本文地址:www.jianshu.com/p/38a4c6451…
目的
最近公司在做一個iOS藍芽專案,在開發的過程中簡單整理了一些與之相關的基礎知識,在這裡分享一下。整理包括以下內容:
1、iOS藍芽開發的關鍵詞
2、藍芽的簡單介紹
3、CoreBluetooth框架
4、實現iOS藍芽外設(Demo)
5、實現iOS藍芽中心裝置(Demo)
Demo的執行gif圖如下,中心裝置可以從外設讀取資料,也可以向外設寫入資料。外設也可以向中心裝置傳送資料。
PS:需要使用真機測試。
iOS的藍芽開發是圍繞著CoreBluetooth框架來實現的。
下面先從iOS藍芽開發的基本概念說起。
一、iOS藍芽開發的關鍵詞
中心裝置:就是用來掃描周圍藍芽硬體的裝置,比如通過你手機的藍芽來掃描並連線智慧手環,這時候你的手機就是中心裝置。
外設:被掃描的裝置。比如當你用手機的藍芽掃描連線智慧手環的時候,智慧手環就是外設。
廣播:就是外設不停的散播藍芽訊號,讓中心裝置可以掃描到。
服務(services):外設廣播和執行的時候會有服務,可以理解成一個功能模組,中心裝置可以讀取服務。外設可以有多個服務。
特徵(characteristic):在服務中的一個單位,一個服務可以有多個特徵,特徵會有一個value,一般讀寫的資料就是這個value。
UUID:區分不同的服務和特徵,可以理解為服務和特徵的身份證。我們可以用UUID來挑選需要的服務和特徵。
二、藍芽的簡單介紹
偷個懶:藍芽百科
藍芽( Bluetooth® ):是一種短距離無線通訊技術 ,可實現固定裝置、移動裝置和樓宇個人域網之間的短距離資料交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍芽4.2釋出於2014年12月2日,本文釋出的時候,藍芽的最新版本為4.2。
三、CoreBluetooth框架
如上圖所示,iOS中的藍芽開發框架CoreBluetooth處在藍芽低功耗協議棧的上面,我們開發的時候只是使用CoreBluetooth這個框架,通過CoreBluetooth可以輕鬆實現外設或中心裝置的開發。
CoreBluetooth可以分為兩大模組,中心裝置central,外設peripheral,它們倆各有自己的一套API供我們使用。
上圖左邊的就是中心裝置的開發類,我們平時是使用CBCentralManager來進行相關操作。
- CBCentralManager: 藍芽中心裝置管理類,用來統一排程中心裝置的開發
- CBPeripheral :藍芽外設,例如藍芽手環、心率監測儀。
- CBService :藍芽外設的服務,可以有0個或者多個服務。
- CBCharacteristic :服務中的特徵,每一個藍芽服務中可以有0個或多個特徵,特徵中包含資料資訊。
- CBUUID:可以理解為服務或特徵的身份證,可以用來選擇需要的服務和特徵。
右邊是外設開發相關類,一般是圍繞著CBPeripheralManager來進行編碼。
- CBPeripheralManager: 藍芽外設開發時使用,用來開發藍芽外設的中心管理類。
- CBCentral:藍芽中心裝置,例如用來連線藍芽手環的手機。
- CBMutableService:外設開發的時候可以新增多個服務,所有這裡用CBMutableService來建立新增服務。
- CBMutableCharacteristic:每個服務中可以有多個特徵,外設開發給服務新增特徵的時候使用這個類。
- CBATTRequest:讀或者寫請求。它的例項物件有一個value屬性,用來裝載外設進行藍芽讀取或寫入請求時的資料。一般在外設寫入或讀取的回撥方法中有這一個引數。
四、實現iOS藍芽外設(Demo)
1、首先匯入CoreBluetooth框架,並遵守協議
#import <CoreBluetooth/CoreBluetooth.h>
// 遵守CBPeripheralManagerDelegate協議
@interface ViewController () <CBPeripheralManagerDelegate>複製程式碼
2、建立外設管理物件,用一個屬性來強引用這個物件。並且在建立的時候設定代理,宣告放到哪個執行緒。
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
// 建立外設管理器,會回撥peripheralManagerDidUpdateState方法
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];複製程式碼
3、當建立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這個引數,只有設定了這個引數,在中心裝置中才能訂閱這個特徵。
一般開發中可以設定兩個特徵,一個用來傳送資料,一個用來接收中心裝置寫過來的資料,我們這裡為了方便就只設定了一個特徵。
最後用一個屬性拿到這個特徵,是為了後面單獨傳送資料的時候使用,資料的寫入和讀取最終還是要通過特徵來完成。
4、當中心裝置讀取這個外設的資料的時候會回撥這個方法。
/** 中心裝置讀取資料的時候回撥 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
// 請求中的資料,這裡把文字框中的資料發給中心裝置
request.value = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
// 成功響應請求
[peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}複製程式碼
5、當中心裝置寫入資料的時候,外設會呼叫下面這個方法。
/** 中心裝置寫入資料的時候回撥 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
// 寫入資料的請求
CBATTRequest *request = requests.lastObject;
// 把寫入的資料顯示在文字框中
self.textField.text = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
}複製程式碼
6、還有一個主動給中心裝置傳送資料的方法。
/** 通過固定的特徵傳送資料到中心裝置 */
- (IBAction)didClickPost:(id)sender {
BOOL sendSuccess = [self.peripheralManager updateValue:[self.textField.text dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristic onSubscribedCentrals:nil];
if (sendSuccess) {
NSLog(@"資料傳送成功");
}else {
NSLog(@"資料傳送失敗");
}
}複製程式碼
7、中心裝置訂閱成功的時候回撥。
/** 訂閱成功回撥 */
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"%s",__FUNCTION__);
}複製程式碼
8、中心裝置取消訂閱的時候回撥。
/** 取消訂閱回撥 */
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"%s",__FUNCTION__);
}複製程式碼
以上就是iOS藍芽外設的基本實現流程,當然還有更多的地方可以進一步處理,這就需要投入更多的時間來學習實驗了。
下面進入iOS藍芽開發的主要部分,中心裝置的實現,這也是手機App通常擔任的角色。
五、實現iOS藍芽中心裝置(Demo)
1、同外設開發一樣,首先要匯入CoreBluetooth框架。
#import <CoreBluetooth/CoreBluetooth.h>複製程式碼
2、遵守的協議與外設開發不同,中心裝置的開發需要遵循如下兩個協議。
@interface ViewController () <CBCentralManagerDelegate,CBPeripheralDelegate>複製程式碼
3、建立中心管理器並用屬性強引用,建立的時候也會設定代理和選擇執行緒。
@property (nonatomic, strong) CBCentralManager *centralManager;
// 建立中心裝置管理器,會回撥centralManagerDidUpdateState
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];複製程式碼
4、當建立中心管理物件的時候,會回撥如下方法用來判斷中心裝置的藍芽狀態。當藍芽狀態沒問題的時候,可以根據外設服務的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(@"藍芽已關閉");
}
}複製程式碼
5、當掃描到外設之後,就會回撥下面這個方法,可以在這個方法中繼續設定篩選條件,例如根據外設名字的字首來選擇,如果符合條件就進行連線。
/** 發現符合要求的外設,回撥 */
- (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];
}複製程式碼
7、當連線成功的時候,就會來到下面這個方法。為了省電,當連線上外設之後,就讓中心裝置停止掃描,並且別忘記設定連線上的外設的代理。在這個方法里根據UUID進行服務的查詢。
/** 連線成功 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
// 可以停止掃描
[self.centralManager stopScan];
// 設定代理
peripheral.delegate = self;
// 根據UUID來尋找服務
[peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
NSLog(@"連線成功");
}複製程式碼
8、連線失敗和斷開連線也有各自的回撥方法。在斷開連線的時候,我們可以設定自動重連,根據專案需求來自定義裡面的程式碼。
/** 連線失敗的回撥 */
-(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];
}複製程式碼
9、下面開始處理代理方法。
最開始就是發現服務的方法。這個方法裡可以遍歷服務,找到需要的服務。由於上面做的外設只有一個服務,所以我這裡直接取服務中的最後一個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];
}複製程式碼
10、下面這個方法裡做的事情不少。
當發現特徵之後,與服務一樣可以遍歷特徵,根據外設開發人員給的文件找出不同特徵,做出相應的操作。
我的外設只設定了一個特徵,所以也是直接通過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方法是對這個特徵進行訂閱,訂閱成功之後,就可以監控外設中這個特徵值得變化了。
11、當訂閱的狀態發生改變的時候,下面的方法就派上用場了。
/** 訂閱狀態的改變 */
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"訂閱失敗");
NSLog(@"%@",error);
}
if (characteristic.isNotifying) {
NSLog(@"訂閱成功");
} else {
NSLog(@"取消訂閱");
}
}複製程式碼
12、外設可以傳送資料給中心裝置,中心裝置也可以從外設讀取資料,當發生這些事情的時候,就會回撥這個方法。通過特種中的value屬性拿到原始資料,然後根據需求解析資料。
/** 接收到資料回撥 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
// 拿到外設傳送過來的資料
NSData *data = characteristic.value;
self.textField.text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}複製程式碼
13、中心裝置可以向外設寫入資料,也可以向外設傳送請求或指令,當需要進行這些操作的時候該怎麼辦呢。
-
首先把要寫入的資料轉化為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(@"寫入成功");
}複製程式碼
14、中心裝置如何主動從外設讀取資料呢。
- 用正在連線的外設物件來呼叫readValueForCharacteristic方法,並且把將要讀取資料的特徵作為引數,這樣就可以主動拿一次資料了。
去到第12步的回撥方法中,在特徵的value屬性中拿到這次的資料。
/** 讀取資料 */
- (IBAction)didClickGet:(id)sender {
[self.peripheral readValueForCharacteristic:self.characteristic];
}複製程式碼
後記
中心裝置的開發是需要配合外設來進行的,一般會有硬體工程師或嵌入式工程師給出通訊協議,根據協議來對專案的各種需求進行操作。