???????近五年做的項(xiàng)目都是和藍(lán)牙有關(guān),網(wǎng)上關(guān)于藍(lán)牙開發(fā)的文章也比比皆是。此文只是記錄我司在實(shí)際工作中遇到的一些特定的應(yīng)用場(chǎng)景和解決小技巧,并對(duì)網(wǎng)絡(luò)上通用的開發(fā)技術(shù)流程的梳理總結(jié)。記錄以備回顧,也分享歡迎交流。
一、特定的使用場(chǎng)景
1、藍(lán)牙外設(shè)設(shè)備升級(jí)等,大數(shù)據(jù)傳輸,耗時(shí)操作
??????? 數(shù)據(jù)發(fā)送時(shí)選擇CBCharacteristicWriteWithResponse,會(huì)影響總交互時(shí)間, 使用CBCharacteristicWriteWithoutResponse又回出現(xiàn)丟包的現(xiàn)象。
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
???????如果交互的總時(shí)間我們不能接受,可以選用CBCharacteristicWriteWithoutResponse模式每包20字節(jié)循環(huán)發(fā),但注意每發(fā)12包延遲100毫秒(經(jīng)驗(yàn)值,12包240字節(jié)小于250),即可加快大數(shù)據(jù)傳輸速率。此方法也分享給使用我司外設(shè)硬件的客戶(多為政企,銀行),截止目前iOS13,屢試不爽。
2、需要藍(lán)牙傳輸在后臺(tái)進(jìn)行
???????我們藍(lán)牙開發(fā)為了能使app在后臺(tái)運(yùn)行時(shí)依舊保持與外設(shè)的連接,就需要在工程目錄下的 info.plist 文件中, iOS9及其以前的做法:新建一行 Required background modes , 加入下面兩項(xiàng)。
App shares data using CoreBluetooth
App communicates using CoreBluetooth
???????高版本的XCode直接在項(xiàng)目的Capablities中,在Background Modes下添加Uses Bluetooth LE accessories即可
3、連接斷開后,在后臺(tái)再次掃描周圍設(shè)備再完成重連。
???????后臺(tái)掃描設(shè)備跟前臺(tái)掃描周圍設(shè)備有一點(diǎn)不同:
???????也許是考慮到功耗的原因,在后臺(tái)只能搜索特定的設(shè)備,所以必須要傳Service UUID。不傳的 話一臺(tái)設(shè)備都搜不到。而這時(shí)就需要外設(shè)在廣播包中有Service UUID,需要廣播包中含有。
4、首次使用APP時(shí)候掃描設(shè)備,并顯示出每臺(tái)設(shè)備的mac地址,點(diǎn)擊設(shè)備進(jìn)行綁定。后續(xù)直接鏈接綁定的設(shè)備。
???????此場(chǎng)景Andriod可以做到,iOS不可以,是由于iOS端掃描到的設(shè)備對(duì)象獲取不到mac地址,需要連接上設(shè)備獲取服務(wù)和 特征值后才能得到mac地址。解決方法可以讓硬件工程師在廣播的時(shí)候添加mac地址,如果不然,只能逐個(gè)連接斷開嘗試,獲取匹配的mac值。
最后我們硬件工程師在廣播包中添加了設(shè)備序列號(hào)。根據(jù)序列號(hào)區(qū)分藍(lán)牙設(shè)備。畢竟序列號(hào)區(qū)別于mac地址,用戶看起來更直觀。
另:推薦LightBlue App,基于CoreBluetooth。是BLE開發(fā)的調(diào)試?yán)鳎揂pp上能獲取的數(shù)據(jù),你就能用代碼實(shí)現(xiàn),軟硬件工程師藍(lán)牙開發(fā)必備。
二、CoreBluetooth框架使用
???????目前藍(lán)牙智能硬件主流都是低功耗的藍(lán)牙4.0技術(shù),由于耗電低,也稱作為BLE(Bluetooth Low Energy)),對(duì)應(yīng)iOS的系統(tǒng)庫(kù)是<CoreBluetooth/CoreBluetooth.h>。另外一種是基于Wi-Fi的連接方式,很少見。我們的設(shè)備都是BLE設(shè)備,以下介紹為CoreBluetooth框架。
核心概念:
- CBCentralManager: 外部設(shè)備管理者
- CBPeripheral: 連接的外部設(shè)備
- CBService: 設(shè)備攜帶的服務(wù)
- CBCharacteristic: 服務(wù)中包含的特征值
使用流程:
創(chuàng)建一個(gè)CBCentralManager實(shí)例
掃描外圍設(shè)備
連接掃描到的設(shè)備
獲得連接設(shè)備的服務(wù)
獲得服務(wù)的特征值
從外圍設(shè)備讀取數(shù)據(jù)
向外圍設(shè)備寫入(發(fā)送)數(shù)據(jù)
三、代碼實(shí)現(xiàn):
1. 初始化
#import <CoreBluetooth/CoreBluetooth.h>
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil];
2. 搜索掃描外圍設(shè)備
/**
* 初始化成功自動(dòng)調(diào)用
* 必須實(shí)現(xiàn)的代理,用來返回創(chuàng)建的centralManager的狀態(tài)。
* CBCentralManagerStatePoweredOn狀態(tài)才可以掃描到外設(shè):
*/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@">>>CBCentralManagerStateUnknown");
break;
case CBCentralManagerStateResetting:
NSLog(@">>>CBCentralManagerStateResetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@">>>CBCentralManagerStateUnsupported");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@">>>CBCentralManagerStateUnauthorized");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@">>>CBCentralManagerStatePoweredOff");
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@">>>CBCentralManagerStatePoweredOn");
/**開始掃描周圍的外設(shè)。
* 第一個(gè)參數(shù)是用來掃描有指定服務(wù)的外設(shè)。
* 傳nil表示掃描所有外設(shè)。成功掃描到外設(shè)后調(diào)用didDiscoverPeripheral
* */
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
break;
default:
break;
}
}
#pragma mark 發(fā)現(xiàn)外設(shè)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"Find device:%@", [peripheral name]);
if (![_deviceDic objectForKey:[peripheral name]]) {
NSLog(@"Find device:%@", [peripheral name]);
if (peripheral!=nil) {
if ([peripheral name]!=nil) {
if ([[peripheral name] hasPrefix:@"過濾"]) {
//連接外設(shè)
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
}
}
}
3.連接外圍設(shè)備
- (void)connectDeviceWithPeripheral:(CBPeripheral *)peripheral
{
[self.centralManager connectPeripheral:peripheral options:nil];
}
#pragma mark 連接成功回調(diào)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
[central stopScan];
peripheral.delegate = self;
self.peripheral = peripheral;
/**
*外設(shè)的服務(wù)、特征、描述等方法是CBPeripheralDelegate的內(nèi)容,
*所以要先設(shè)置代理peripheral.delegate = self
*參數(shù)表示你要的服務(wù)的UUID,nil表示掃描所有服務(wù)。
*成功發(fā)現(xiàn)服務(wù),回調(diào)didDiscoverServices
*/
[peripheral discoverServices:@[[CBUUID UUIDWithString:@"服務(wù)UUID"]]];
}
#pragma mark 連接失敗回調(diào)
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%@", error);
}
#pragma mark 取消與外設(shè)的連接回調(diào)
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%@", peripheral);
}
4. 獲得外圍設(shè)備的服務(wù)
#pragma mark 發(fā)現(xiàn)服務(wù)回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
CBService * __nullable findService = nil;
// 遍歷服務(wù)
for (CBService *service in peripheral.services)
{
if ([[service UUID] isEqual:[CBUUID UUIDWithString:@"服務(wù)UUID"]])
{
findService = service;
}
}
if (findService)
[peripheral discoverCharacteristics:NULL forService:findService];
}
5、獲得服務(wù)的特征;
#pragma mark 發(fā)現(xiàn)特征回調(diào)
/**發(fā)現(xiàn)特征后,可以根據(jù)特征的properties進(jìn)行:
*讀readValueForCharacteristic、寫writeValue、訂閱通知setNotifyValue、
*掃描特征的描述discoverDescriptorsForCharacteristic
*/
- (void)peripheral:(CBPeripheral *)peripheraldidDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"特征UUID"]]) {
//讀取成功回調(diào)didUpdateValueForCharacteristic
self.characteristic = characteristic;
//接收一次
//[peripheral readValueForCharacteristic:characteristic];
//訂閱, 實(shí)時(shí)接收
[peripheral setNotifyValue:YES forCharacteristic:characteristic];=
// 發(fā)送下行數(shù)據(jù)
NSData *data = [@"帶發(fā)送的數(shù)據(jù)"dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
//當(dāng)發(fā)現(xiàn)characteristic有descriptor,回調(diào)didDiscoverDescriptorsForCharacteristic
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
6.從外圍設(shè)備讀取數(shù)據(jù)
#pragma mark - 獲取數(shù)據(jù)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSData *data = [characteristic.value dataUsingEncoding:NSUTF8StringEncoding];
}
#pragma mark - 讀取外設(shè)實(shí)時(shí)數(shù)據(jù)
- (void)peripheral:(CBPeripheral *)peripheraldidUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristicerror:(NSError *)error{
if (characteristic.isNotifying) {
[peripheral readValueForCharacteristic:characteristic];
}
else {
//這里出錯(cuò) 一般是連接斷開了
NSLog(@"%@", characteristic);
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
7. 給外圍設(shè)備發(fā)送(寫入)數(shù)據(jù)
/** 根據(jù)獲取到的外圍設(shè)備的寫的服務(wù)特征值 向藍(lán)牙設(shè)備發(fā)送數(shù)據(jù)
*藍(lán)牙發(fā)送數(shù)據(jù)長(zhǎng)度為20字節(jié),待發(fā)送數(shù)據(jù)大于20字節(jié)需要分包發(fā)送,
*特別長(zhǎng)的數(shù)據(jù)耗時(shí)較長(zhǎng),解決辦法在文章的開始
*/
- (void)sendData
{
NSData *data = [@"待發(fā)送數(shù)據(jù)" dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
#pragma mark 數(shù)據(jù)寫入成功回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"寫入成功");
}
8. 停止掃描、斷開連接
#pragma mark 斷開連接
- (void)disConnectPeripheral{
/**斷開連接后回調(diào)didDisconnectPeripheral
*注意斷開后如果要重新掃描這個(gè)外設(shè),
*需要重新調(diào)用[self.centralManager scanForPeripheralsWithServices:nil options:nil];
*/
[self.centralManager cancelPeripheralConnection:self.peripheral];
}
- (void)scanDevice
{
if (_centralManager == nil) {
self.centralManager = [[CBCentralManager alloc] initWithDelegate:selfqueue:nil];
[_deviceDic removeAllObjects];
}
}
#pragma mark 停止掃描外設(shè)
- (void)stopScanPeripheral{
[self.centralManager stopScan];
}