1.什么是藍(lán)牙4.0,藍(lán)牙其它標(biāo)準(zhǔn)又是什么?
創(chuàng)新互聯(lián)網(wǎng)站建設(shè)提供從項(xiàng)目策劃、軟件開(kāi)發(fā),軟件安全維護(hù)、網(wǎng)站優(yōu)化(SEO)、網(wǎng)站分析、效果評(píng)估等整套的建站服務(wù),主營(yíng)業(yè)務(wù)為成都做網(wǎng)站、成都網(wǎng)站制作,App定制開(kāi)發(fā)以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。創(chuàng)新互聯(lián)深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
詳細(xì)描述:低功耗藍(lán)牙(Low Energy; LE),又視為Bluetooth Smart或藍(lán)牙核心規(guī)格4.0版本。其特點(diǎn)具備節(jié)能、便于采用,是藍(lán)牙技術(shù)專(zhuān)為物聯(lián)網(wǎng)(Internet of Things; IOT)開(kāi)發(fā)的技術(shù)版本。所以它最主要的特點(diǎn)是低功耗,普及率高?,F(xiàn)在所說(shuō)的藍(lán)牙設(shè)備,大部分都是在說(shuō)4.0設(shè)備,ble也特指4.0設(shè)備。 在4.0之前重要的版本有 2.1版本-基本速率/增強(qiáng)數(shù)據(jù)率(BR/EDR) 和 3.0 高速藍(lán)牙 版本,這些統(tǒng)稱(chēng)為經(jīng)典藍(lán)牙。4.0還有4.1和4.2的小版本,其中4.2版本對(duì)傳輸速率做了進(jìn)一步他提升,提高了2.5倍,蘋(píng)果從iphone6開(kāi)始使用4.2,最新的藍(lán)牙標(biāo)準(zhǔn)為藍(lán)牙5.0,其中最大的特點(diǎn)連接范圍擴(kuò)大了4倍,速度又提高了2倍,無(wú)連接數(shù)據(jù)廣播能力提高了8倍,增加了藍(lán)牙組網(wǎng)的能力。
2.藍(lán)牙開(kāi)發(fā)必須知道的概念。
2.1.1 central和peripheral:
藍(lán)牙應(yīng)用開(kāi)發(fā)中,存在兩種角色,分別是central和peripheral(p?’r?f?r?l) ,中文就是中心和外設(shè)。比如手機(jī)去連接智能設(shè)備,那手機(jī)就是central,智能設(shè)備就是peripheral。大多時(shí)候都是central去連接peripheral的場(chǎng)景。
2.1.2 廣播和連接:
peripheral會(huì)發(fā)出廣播,central掃描到廣播后,可以對(duì)設(shè)備進(jìn)行連接,發(fā)出connect請(qǐng)求,peripheral接收到請(qǐng)求后,同意連接后,central和peripheral就建立了連接。
2.1.3?連接后的操作:
write,read,notify,indecate, response or not …
indecate和notify的區(qū)別就在于,indecate是一定會(huì)收到數(shù)據(jù),notify有可能會(huì)丟失數(shù)據(jù)(不會(huì)有central收到數(shù)據(jù)的回應(yīng)),write也分為response和noresponse,如果是response,那么write成功回收到peripheral的確認(rèn)消息,但是會(huì)降低寫(xiě)入的速率。
2.1.4 協(xié)議:
每個(gè)具體的智能設(shè)備,都約定了一組數(shù)據(jù)格式,這個(gè)就是數(shù)據(jù)協(xié)議,例如手環(huán)中獲取到數(shù)據(jù)0X001023,其中第2位到第5位表示步數(shù),那么就2310就是步數(shù)的16進(jìn)制的數(shù)據(jù),轉(zhuǎn)換成10進(jìn)制就是8976步,需要注意的是,設(shè)備端都是小端模式,所以取4位時(shí)候,高字節(jié)在前低字節(jié)在后。
3. iOS藍(lán)牙應(yīng)用的一般開(kāi)發(fā)流程。
4. 藍(lán)牙的數(shù)據(jù)交互。
write,read,notify,indecate, response or not … 都是容易理解的,indecate和notify對(duì)應(yīng)的是長(zhǎng)連接,建立indecate后,peripheral可以隨時(shí)往central發(fā)送數(shù)據(jù)。
indecate和notify的區(qū)別就在于,indecate是一定會(huì)收到數(shù)據(jù),notify有可能會(huì)丟失數(shù)據(jù)(不會(huì)有central收到數(shù)據(jù)的回應(yīng)),write也分為response和noresponse,如果是response,那么write成功回收到peripheral的確認(rèn)消息,但是會(huì)降低寫(xiě)入的速率。
對(duì)于一個(gè)charateristic,他的讀寫(xiě)訂閱的權(quán)限是peripheral決定的,熟悉可以被同時(shí)設(shè)置,一般會(huì)根據(jù)外設(shè)的功能來(lái)決定。
5.藍(lán)牙ota DFU。
藍(lán)牙ota,DFU(Device Firmware Update)指的是藍(lán)牙設(shè)備的固件升級(jí),其實(shí)是一整套流程,不同的藍(lán)牙芯片,ota的流程有不同之處,我這里用ti的芯片舉例。步驟為:切系統(tǒng)(bootloader mode),重啟,傳輸數(shù)據(jù),驗(yàn)證數(shù)據(jù),切系統(tǒng),重啟,完成。
其中數(shù)據(jù)傳輸也會(huì)分成很多節(jié)去發(fā)送,沒(méi)法送一段數(shù)據(jù),做一次數(shù)據(jù)校驗(yàn)。
6.ota存在的問(wèn)題。
每個(gè)智能設(shè)備的速率,功耗,存儲(chǔ)都會(huì)有很多限制,導(dǎo)致很多設(shè)備會(huì)自己去實(shí)現(xiàn)ota的功能,自定義流程和數(shù)據(jù)傳輸方式,導(dǎo)致許多設(shè)備都是有自己私有的ota模式和協(xié)議,所以在做開(kāi)發(fā)的時(shí)候,要仔細(xì)閱讀設(shè)備協(xié)議中對(duì)ota的描述。
7.如何做自動(dòng)重連。
只需要在設(shè)備斷開(kāi)連接的委托方法中,重新調(diào)用gatt.connet或者是centralManager.connet方法就可以了,無(wú)論當(dāng)時(shí)設(shè)備是否有點(diǎn),是否在周?chē)?,?dāng)設(shè)備再次開(kāi)會(huì)或者連接到可連接范圍內(nèi),都會(huì)自動(dòng)被連上。
8.連接失敗處理。
分兩個(gè)平臺(tái)來(lái)說(shuō),iOS端也有連接失敗的委托,但是好像幾乎不會(huì)發(fā)生這種情況,而對(duì)于同款設(shè)備,android常常會(huì)出現(xiàn)連接失敗的情況,status != BluetoothGatt.GATT_SUCCESS,android端開(kāi)發(fā)請(qǐng)不要把連接失敗和斷開(kāi)連接放在一塊處理,因?yàn)閿嚅_(kāi)連接可以直接嘗試重新連接,而連接失敗后嘗試重新連接,需要加一些延時(shí),并且需要gatt.close,清空一下?tīng)顟B(tài),否則會(huì)把gatt阻塞導(dǎo)致手機(jī)不重啟藍(lán)牙就再也無(wú)法連接任何設(shè)備的情況 。
9.后臺(tái)運(yùn)行。
iOS后來(lái)運(yùn)行,需要設(shè)備中info.Plist權(quán)限,key:Required background modes ,value: bluetooth-central(手機(jī)作為central) , bluetooth-peripheral。
10.同時(shí)連接多個(gè)設(shè)備。
使用同一個(gè)CBCentralManager,通過(guò)進(jìn)入委托的peripheral的identifier區(qū)分不同的設(shè)備,進(jìn)行不同的操作和處理。
11.掃描廣播包。
所有外設(shè),只有在發(fā)出廣播包的情況下,才能被central發(fā)現(xiàn),絕大多數(shù)情況下,外設(shè)被連接后就不會(huì)發(fā)出廣播(也有例外),很多人遇到無(wú)法找到設(shè)備的問(wèn)題,大多屬于這種情況。
12.提高藍(lán)牙連接速度。
無(wú)論是iOS,還是android,都可以通過(guò)已綁定的設(shè)備,在不開(kāi)啟掃描的情況下進(jìn)行快速連接,iOS需要的參數(shù)是peripheral的identifier,android需要mac地址。但android和iOS還是有一些區(qū)別的,比如iOS不能拿到已綁定的設(shè)備list,但是可以通過(guò)UUID去拿到peripheral的實(shí)例。而android可以拿到已綁定的設(shè)備list。android綁定過(guò)程需要手動(dòng)調(diào)用createBond的方法,而iOS在連接成功一次后會(huì)自動(dòng)綁定。 android在處理createBond時(shí),常常會(huì)應(yīng)為不同手機(jī)平臺(tái),不同設(shè)備,會(huì)產(chǎn)生兼容性的問(wèn)題,這點(diǎn)需要注意。
13.定向掃描。
在掃描時(shí)候可以傳入serviceUUID,這樣可以掃描到特定條件的設(shè)備,提高掃描的速度,排除干擾。
14.如何獲取mac地址。
而iOS出于蘋(píng)果的安全策略問(wèn)題,無(wú)法直接獲得mac地址,只能得到一個(gè)mac地址換算出來(lái)的identifier。
藍(lán)牙開(kāi)發(fā)說(shuō)簡(jiǎn)單也簡(jiǎn)單,說(shuō)不簡(jiǎn)單也有點(diǎn)難,開(kāi)發(fā)人員在首次開(kāi)發(fā)藍(lán)牙前首先需要搞清楚藍(lán)牙開(kāi)發(fā)的概念,還要了解掌握藍(lán)牙開(kāi)發(fā)的一整套流程,這樣才能快速上手開(kāi)發(fā)藍(lán)牙。
? 藍(lán)牙開(kāi)發(fā)分為兩種模式:管理者模式和中心者模式。管理者模式基本很少用到,相當(dāng)于iPhone手機(jī)作為外設(shè),自己創(chuàng)建服務(wù)和特性,然后用其他設(shè)備連接iPhone手機(jī);中心者模式一般是大部分情況下都會(huì)使用的,使用中心者模式開(kāi)發(fā)相當(dāng)于iPhone手機(jī)作為主機(jī),連接藍(lán)牙外設(shè),下面介紹藍(lán)牙開(kāi)發(fā)的例子就是使用的中心者模式來(lái)講解的。
在這里我還是要推薦下我自己建的iOS開(kāi)發(fā)學(xué)習(xí)群:680565220,群里都是學(xué)ios開(kāi)發(fā)的,如果你正在學(xué)習(xí)ios ,我歡迎你加入,今天分享的這個(gè)案例已經(jīng)上傳到群文件,大家都是軟件開(kāi)發(fā)黨,不定期分享干貨(只有iOS軟件開(kāi)發(fā)相關(guān)的),包括我自己整理的一份2018最新的iOS進(jìn)階資料和高級(jí)開(kāi)發(fā)教程
一、關(guān)于藍(lán)牙開(kāi)發(fā)的一些重要的理論概念:
1、服務(wù)(services):藍(lán)牙外設(shè)對(duì)外廣播的時(shí)候一定會(huì)有一個(gè)服務(wù),有些時(shí)候也可以是有多個(gè)服務(wù),服務(wù)下面包含一些特性,服務(wù)可以理解成一個(gè)模塊的窗口;
2、特征(characteristic):特征存在于服務(wù)下面的,一個(gè)服務(wù)下面可以有多個(gè)特征,特征可以理解成具體實(shí)現(xiàn)功能的窗口,一般的特性都會(huì)有value,也就是特征值,是特征和外界交互的最小單位;
? 3、UUID:藍(lán)牙上的唯一標(biāo)示符,為了區(qū)分不同服務(wù)和特征,就用UUID來(lái)表示。
二、藍(lán)牙連接的主要步驟
?1、創(chuàng)建一個(gè)CBCentralManager實(shí)例來(lái)進(jìn)行藍(lán)牙管理;
?2、搜索掃描外圍設(shè)備;
?3、連接外圍設(shè)備;
?4、獲得外圍設(shè)備的服務(wù);
?5、獲得服務(wù)的特征;
?6、從外圍設(shè)備讀取數(shù)據(jù);
?7、給外圍設(shè)備發(fā)送(寫(xiě)入)數(shù)據(jù)。
三、藍(lán)牙連接和數(shù)據(jù)讀寫(xiě)的具體步驟
?1、導(dǎo)入蘋(píng)果系統(tǒng)藍(lán)牙框架
#import
?2、遵循兩個(gè)藍(lán)牙框架相關(guān)的協(xié)議
3、新建兩個(gè)實(shí)例屬性,一個(gè)特征屬性
@property (nonatomic, strong) CBCentralManager *centralManager; //中心管理者
@property (nonatomic, strong) CBPeripheral *peripheral; //連接到的外設(shè)
@property (nonatomic, strong) CBCharacteristic *characteristic; //特征
?4、初始化CBCentralManager,進(jìn)行藍(lán)牙管理
- (void)viewDidLoad {
[super viewDidLoad];
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()]; ? ? //創(chuàng)建實(shí)例進(jìn)行藍(lán)牙管理
}
//若中心管理者初始化之后 就會(huì)觸發(fā)下面這個(gè)代理方法 該代理方法是用來(lái)判斷手機(jī)藍(lán)牙的狀態(tài)的
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
// 藍(lán)牙可用,開(kāi)始掃描外設(shè)
if (central.state == CBManagerStatePoweredOn) {
NSLog(@"藍(lán)牙可用");
//在中心管理者成功開(kāi)啟之后再進(jìn)行一些操作
//搜索掃描外設(shè)
// 根據(jù)SERVICE_UUID來(lái)掃描外設(shè),如果不設(shè)置SERVICE_UUID,則掃描所有藍(lán)牙設(shè)備
// [self.centralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:SERVICE_UUID]]}];
[central scanForPeripheralsWithServices:nil options:nil];
}
if(central.state == CBManagerStateUnsupported) {
NSLog(@"該設(shè)備不支持藍(lán)牙");
}
if (central.state == CBManagerStatePoweredOff) {
NSLog(@"藍(lán)牙已關(guān)閉");
}
if (central.state == CBManagerStateUnknown) {
NSLog(@"藍(lán)牙當(dāng)前狀態(tài)不明確");
}
if (central.state == CBManagerStateUnauthorized) {
NSLog(@"藍(lán)牙未被授權(quán)");
}
}
? 5、搜索外圍設(shè)備
//執(zhí)行掃描動(dòng)作之后,如果掃描到外設(shè)了,就會(huì)自動(dòng)回調(diào)下面的協(xié)議方法
/** 發(fā)現(xiàn)符合要求的外設(shè),回調(diào) */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
NSLog(@"%@====",peripheral.name);
//根據(jù)外設(shè)名字有選擇性的篩選連接藍(lán)牙設(shè)備
if ([peripheral.name hasPrefix:@"TEAMOSA"]) {
//在這里對(duì)外設(shè)攜帶的廣播數(shù)據(jù)進(jìn)行進(jìn)一步的處理
if ([self.peripheraNames containsObject:peripheral.name]) {
//如果數(shù)組中包含了就不再添加
return;
}
//添加到外設(shè)名字?jǐn)?shù)組中
[self.peripheraNames addObject:peripheral.name];
//標(biāo)記外設(shè),讓它的生命周期與控制器的一致
self.peripheral = peripheral;
// 可以根據(jù)外設(shè)名字來(lái)過(guò)濾外設(shè)
// [central connectPeripheral:peripheral options:nil];
}
// 連接外設(shè)
// [central connectPeripheral:peripheral options:nil];
}
6、連接外圍設(shè)備
//連接外圍設(shè)備,中心管理者連接外設(shè)成功,如果連接成功就會(huì)回調(diào)這個(gè)協(xié)議方法
/** 連接成功 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//連接成功之后,可以進(jìn)行服務(wù)和特性的發(fā)現(xiàn)。 停止中心管理設(shè)備的掃描動(dòng)作,要不然在你和已經(jīng)連接好的外設(shè)進(jìn)行數(shù)據(jù)溝通時(shí),如果又有一個(gè)外設(shè)進(jìn)行廣播且符合你的連接條件,那么你的iOS設(shè)備也會(huì)去連接這個(gè)設(shè)備(因?yàn)閕OS BLE4.0是支持一對(duì)多連接的),導(dǎo)致數(shù)據(jù)的混亂。
//停止掃描動(dòng)作
[self.centralManager stopScan];
// 設(shè)置外設(shè)的代理
peripheral.delegate = self;
// 根據(jù)UUID來(lái)尋找服務(wù)
// [peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
//外設(shè)發(fā)現(xiàn)服務(wù),傳nil代表不過(guò)濾,一次性讀出外設(shè)的所有服務(wù)
[peripheral discoverServices:nil];
NSLog(@"%s, line = %d, %@=連接成功", __FUNCTION__, __LINE__, peripheral.name);
}
//外設(shè)連接失敗
/** 連接失敗的回調(diào) */
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"%s, line = %d, %@=連接失敗", __FUNCTION__, __LINE__, peripheral.name);
}
//丟失連接 掉線
/** 斷開(kāi)連接 */
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
NSLog(@"%s, line = %d, %@=斷開(kāi)連接", __FUNCTION__, __LINE__, peripheral.name);
// 斷開(kāi)連接可以設(shè)置重新連接
[central connectPeripheral:peripheral options:nil];
}
7、獲取外圍設(shè)備服務(wù)和特征
/** 發(fā)現(xiàn)服務(wù) */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
// 遍歷出外設(shè)中所有的服務(wù)
for (CBService *service in peripheral.services) {
// NSLog(@"所有的服務(wù):%@",service);
}
// 這里僅有一個(gè)服務(wù),所以直接獲取
CBService *service = peripheral.services.lastObject;
// 根據(jù)UUID尋找服務(wù)中的特征
// [peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service];
// [peripheral discoverCharacteristics:@[service.UUID] forService:service];
[peripheral discoverCharacteristics:nil forService:service];
}
8、從外圍設(shè)備讀取數(shù)據(jù)
// 更新特征的value的時(shí)候會(huì)調(diào)用 (凡是從藍(lán)牙傳過(guò)來(lái)的數(shù)據(jù)都要經(jīng)過(guò)這個(gè)回調(diào),簡(jiǎn)單的說(shuō)這個(gè)方法就是你拿數(shù)據(jù)的唯一方法) 你可以判斷是否 從外圍設(shè)備讀數(shù)據(jù)
/** 接收到數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
// if (characteristic == @"你要的特征的UUID或者是你已經(jīng)找到的特征") {
// //characteristic.value就是你要的數(shù)據(jù)
// }
if ([peripheral.name hasPrefix:@"TEAMOSA"]){
NSData *data = characteristic.value;
NSString *value = [self hexadecimalString:data];
// NSLog(@"characteristic(讀取到的): %@, data : %@, value : %@", characteristic, data, value);
}
// 拿到外設(shè)發(fā)送過(guò)來(lái)的數(shù)據(jù)
// NSData *data = characteristic.value;
// self.textFild.text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
9、向外圍設(shè)備發(fā)送(寫(xiě)入)數(shù)據(jù)
//這個(gè)方法你可以放在button的響應(yīng)里面,也可以在找到特征的時(shí)候就寫(xiě)入,具體看你業(yè)務(wù)需求怎么用
//[self.peripherale writeValue:_batteryData forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];//第一個(gè)參數(shù)是已連接的藍(lán)牙設(shè)備; 第二個(gè)參數(shù)是要寫(xiě)入到哪個(gè)特征; 第三個(gè)參數(shù)是通過(guò)此響應(yīng)記錄是否成功寫(xiě)入 需要注意的是特征的屬性是否支持寫(xiě)數(shù)據(jù)
/** 寫(xiě)入數(shù)據(jù)回調(diào) */
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
/*
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(NA, 6_0) = 0x100,
CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200
};
打印出特征的權(quán)限(characteristic.properties),可以看到有很多種,這是一個(gè)NS_OPTIONS的枚舉,可以是多個(gè)值
常見(jiàn)的又read,write,noitfy,indicate.知道這幾個(gè)基本夠用了,前倆是讀寫(xiě)權(quán)限,后倆都是通知,倆不同的通知方式
*/
// NSLog(@"%s, line = %d, char.pro = %d", __FUNCTION__, __LINE__, characteristic.properties);
// 此時(shí)由于枚舉屬性是NS_OPTIONS,所以一個(gè)枚舉可能對(duì)應(yīng)多個(gè)類(lèi)型,所以判斷不能用 = ,而應(yīng)該用包含
NSLog(@"write value success(寫(xiě)入成功) : %@", characteristic);
}
10、具體調(diào)用給藍(lán)牙外設(shè)寫(xiě)入數(shù)據(jù)方法,這里的例子是以按鈕點(diǎn)擊事件里面來(lái)調(diào)用處理
//發(fā)送按鈕點(diǎn)擊事件
- (void)sendClick {
if (!self.characteristic) {
return;
}
_tempValue = [NSString stringWithFormat:@"%.0f", progressView.centigradeDegree];
_timeValue = [NSString stringWithFormat:@"%.0ld", (long)progressView1.timeDegree];
NSString *ttData = [NSString stringWithFormat:@"%@,%@U", _tempValue, _timeValue];
// NSString *aaa = [DataCoverTool coverFromStringToHexStr:ttData];
// 用NSData類(lèi)型來(lái)寫(xiě)入
// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arry];
NSData *data = [ttData dataUsingEncoding:NSUTF8StringEncoding];
// NSData *data = [self dataWithString:ttData];
// 根據(jù)上面的特征self.characteristic來(lái)寫(xiě)入數(shù)據(jù)
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
iOS 藍(lán)牙開(kāi)發(fā)(二)
iOS 藍(lán)牙開(kāi)發(fā)(三)
iOS 藍(lán)牙開(kāi)發(fā)(四)
在iOS中藍(lán)牙相關(guān)實(shí)現(xiàn)都是在CoreBluetooth這個(gè)framework中的,所以我們創(chuàng)建一個(gè)單例類(lèi)中需要先導(dǎo)入 #import CoreBluetooth/CoreBluetooth.h ,再后即可使用這個(gè)單例類(lèi)進(jìn)行管理我們藍(lán)牙的掃描、連接、狀態(tài)等實(shí)現(xiàn)。
當(dāng) central.state 為CBManagerStatePoweredOn即可開(kāi)始掃描, 具體方法 [self.centralManager scanForPeripheralsWithServices:nil options:nil] 當(dāng)調(diào)用 scanForPeripheralsWithServices:options: 函數(shù)時(shí)就會(huì)實(shí)時(shí)調(diào)用其代理方法 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
peripheral 是外設(shè)類(lèi) advertisementData 是廣播的值,一般攜帶設(shè)備名, serviceUUID 等信息。 RSSI 絕對(duì)值越大,表示信號(hào)越差,設(shè)備離的越遠(yuǎn)。如果想裝換成百分比強(qiáng)度, (RSSI+100)/1001 (這是一個(gè)約數(shù),藍(lán)牙信號(hào)值并不一定是-100 - 0的值)
藍(lán)牙的連接是當(dāng)中心設(shè)備掃描到可用外設(shè)后, 利用函數(shù) [self.centralManager connectPeripheral:peripheral options:nil]; 進(jìn)行鏈接, 當(dāng)函數(shù)被調(diào)用后, 就會(huì)回調(diào)其對(duì)應(yīng)的代理函數(shù)。
本篇筆記主要是記錄如何初始化藍(lán)牙的 CBCentralManager 的中心管理類(lèi),并記錄如何實(shí)現(xiàn)掃描周邊外設(shè)、如何鏈接、獲取藍(lán)牙當(dāng)前狀態(tài)。
總結(jié)一下藍(lán)牙開(kāi)發(fā)相關(guān)的知識(shí)點(diǎn)和注意事項(xiàng),做個(gè)筆記,也希望你們能少踩坑
(公司部分藍(lán)牙項(xiàng)目為混編項(xiàng)目,藍(lán)牙相關(guān)處理均采用了Objective-C,故本文????均采用OC,Swift處理相同)
藍(lán)牙4.0包含兩個(gè)藍(lán)牙標(biāo)準(zhǔn),它是一個(gè)是 雙模 的標(biāo)準(zhǔn),它包含 傳統(tǒng)藍(lán)牙部分(也稱(chēng)經(jīng)典藍(lán)牙) 和 低功耗藍(lán)牙部分(BLE) , 二者適用于不同的應(yīng)用場(chǎng)景和應(yīng)用條件。他們的特點(diǎn)如下
所以藍(lán)牙4.0是集成了傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙兩個(gè)標(biāo)準(zhǔn)的,并不只是低功耗藍(lán)牙
藍(lán)牙4.0支持兩種部署方式: 雙模式 和 單模式 ,雙模同時(shí)支持經(jīng)典藍(lán)牙和低功耗藍(lán)牙,而單模則只支持其中一種。
二者更多細(xì)節(jié)詳見(jiàn): 傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙的區(qū)別
iOS中藍(lán)牙相關(guān)功能都封裝進(jìn)了 CoreBluetooth 類(lèi)中,其中有幾個(gè)常見(jiàn)的參數(shù)和概念
具體API參考 CoreBluetooth藍(lán)牙開(kāi)發(fā)
保存到數(shù)組中的設(shè)備可通過(guò) UUID 來(lái)進(jìn)行區(qū)分。從 iOS7之后蘋(píng)果不提供外設(shè)的mac地址,外設(shè)的唯一標(biāo)識(shí)換成了由mac封裝加密后的UUID,需要注意的是不同的手機(jī)獲取同一個(gè)外設(shè)的UUID是不同的,所以在不同手機(jī)之間UUID不是唯一的,但在本機(jī)上可以作為唯一標(biāo)識(shí)(特殊情況手機(jī)刷機(jī)后也會(huì)改變UUID)。
如何獲取Mac地址
一般使用場(chǎng)景是根據(jù)Mac地址區(qū)分某個(gè)外設(shè)
注意點(diǎn):
寫(xiě)入數(shù)據(jù)時(shí)可能會(huì)遇到需要分包發(fā)送的情況,我們可以通過(guò)下面的API或許當(dāng)前特征支持的最大的單條寫(xiě)入長(zhǎng)度
maxLength 一般取決于藍(lán)牙模塊內(nèi)部接收 緩沖區(qū) 的大小,很多硬件設(shè)備這個(gè)緩沖區(qū)的大小是 20 字節(jié), 這個(gè)大小也和特征的寫(xiě)入權(quán)限有關(guān),像具有寫(xiě)入權(quán)限 withResponse 類(lèi)的特征其大小一般為 512 字節(jié),當(dāng)然這些都是取決于設(shè)備測(cè)的設(shè)置;
當(dāng)我們單次發(fā)送的數(shù)據(jù)字節(jié)長(zhǎng)度大于 maxLength 時(shí),我們就需要采用分包的方式來(lái)發(fā)送數(shù)據(jù)了,
分包發(fā)送的邏輯類(lèi)似于下面
這邊延時(shí)主要是設(shè)備側(cè)的接收模塊接收數(shù)據(jù)以及處理能力有限
外圍設(shè)備測(cè)和中心設(shè)備(大部分情況下是手機(jī))保持藍(lán)牙連接的狀態(tài)下,如果長(zhǎng)時(shí)間不產(chǎn)生交互,藍(lán)牙就會(huì)斷開(kāi),所以為了保持兩者持續(xù)的連接狀態(tài),需要做保活處理,也就是需要持續(xù)的發(fā)送心跳包(watchdog)。相應(yīng)的處理是使用一個(gè)定時(shí)器定時(shí)向設(shè)備側(cè)發(fā)送符合設(shè)備協(xié)議格式的心跳包。
斷開(kāi)連接很簡(jiǎn)單,只需要調(diào)用 [self.centralManager cancelPeripheralConnection:peripheral] 傳入需要斷開(kāi)連接的設(shè)備對(duì)象就行了。斷開(kāi)連接時(shí)會(huì)自動(dòng)調(diào)用 centralManager:didDisconnectPeripheral:error: 代理方法。
按照之前的慣例,當(dāng)error為nil時(shí)表示斷開(kāi)成功,error不為nil時(shí)斷開(kāi)失敗。這種理解是錯(cuò)誤的。
當(dāng)你調(diào)用 cancelPeripheralConnection: 方法(主動(dòng)斷開(kāi))斷開(kāi)連接時(shí)error為nil ; 沒(méi)有調(diào)用這個(gè)方法(異常斷開(kāi))而斷開(kāi)時(shí)error返回的是異常斷開(kāi)的原因。也可以理解為主動(dòng)調(diào)用斷開(kāi)連接方法一定會(huì)斷開(kāi)
接下來(lái)就是斷開(kāi)重連的問(wèn)題了,對(duì)藍(lán)牙功能進(jìn)行封裝時(shí)肯定少不了斷開(kāi)重連。首先斷開(kāi)時(shí)可通過(guò)上面的代理方法的error是否為nil判斷是否是異常斷開(kāi),一般情況下異常斷開(kāi)時(shí)是需要重連的
原因就是當(dāng)設(shè)備斷開(kāi)連接后 peripheral.services 為nil了,當(dāng)然 service.characteristics 也是nil,所以需要在斷開(kāi)連接時(shí)把保存這個(gè)設(shè)備對(duì)應(yīng)的服務(wù)和特征全部清除,然后在連接成功時(shí)重新過(guò)一遍發(fā)現(xiàn)服務(wù)和發(fā)現(xiàn)特征的流程就好了。
iOS7 開(kāi)始,Apple加入了Beacon圍欄檢測(cè)的API, ( iBeacon-維基百科 ), 其工作方式是,配備有低功耗藍(lán)牙(BLE)通信功能的設(shè)備使用 BLE 技術(shù)向周?chē)l(fā)送自己特有的 ID,接收到該 ID 的應(yīng)用軟件會(huì)根據(jù)該 ID 采取一些行動(dòng)。比如,在店鋪里設(shè)置 iBeacon 通信模塊的話,便可讓 iPhone 和 iPad 上運(yùn)行一資訊告知服務(wù)器,或者由服務(wù)器向顧客發(fā)送折扣券及進(jìn)店積分, 或者公司的手機(jī)打卡,只要手機(jī)靠近打卡器一定范圍,手機(jī)測(cè)就向打開(kāi)器發(fā)送打卡信息,從而自動(dòng)打卡。這種場(chǎng)景還有很多。 其中一個(gè)最重要的功能就是App的喚醒功能(殺死后也能喚醒)
舉一個(gè)我們的例子,我們的產(chǎn)品業(yè)務(wù)場(chǎng)景就是在進(jìn)入車(chē)輛以后,需要使用藍(lán)牙連接我們的后裝車(chē)載設(shè)備以采集車(chē)輛信息和駕駛行為行程等,這里有一個(gè)問(wèn)題就是在App被殺死的情況下如何喚醒App, 因?yàn)椴豢赡芤笥脩裘看味贾鲃?dòng)去打開(kāi)App,這樣體驗(yàn)太差。我們的做法是通過(guò)iBeacon,當(dāng)我們的車(chē)輛點(diǎn)火以后,設(shè)備測(cè)通電,發(fā)出 iBeacon廣播 ,App實(shí)現(xiàn)監(jiān)聽(tīng)iBeacon相關(guān)功能后就可以喚醒我們App,然后在相應(yīng)的回調(diào)的處理一些事情,比如通過(guò)藍(lán)牙連接設(shè)備。這里的前提條件是我們的硬件設(shè)備測(cè)包含iBeacon模塊,具有iBeacon功能,而且對(duì)iBeacon的廣播頻率也有一定的要求,長(zhǎng)了可能喚醒的功能會(huì)不穩(wěn)定,官方建議的好像是100ms,頻率超高越耗電,但可以讓手機(jī)或其它監(jiān)聽(tīng)設(shè)備越快地發(fā)現(xiàn)iBeacon。標(biāo)準(zhǔn)的BLE廣播距離是100m,這使Beacon在室內(nèi)位置跟蹤場(chǎng)景下的效果更理想。
關(guān)于iBeacon更多的使用及介紹請(qǐng)參考
蘋(píng)果核 - iOS端近場(chǎng)圍欄檢測(cè)(一) ——iBeacon
iBeacon技術(shù)初探
GAP(Generic Access Profile):它用來(lái)控制設(shè)備連接和廣播,GAP 使你的設(shè)備被其他設(shè)備可見(jiàn),并決定了你的設(shè)備是否可以或者怎樣與合同設(shè)備進(jìn)行交互。
GATT(Generic Attribute Profile):BLE連接都是建立在GATT協(xié)議之上的。GATT 是一個(gè)在藍(lán)牙連接之上的發(fā)送和接收很短的數(shù)據(jù)段的通用規(guī)范,這些很短的數(shù)據(jù)段被稱(chēng)為屬性(Attribute)。
BLE中主要有兩個(gè)角色:外圍設(shè)備(Peripheral)和中心設(shè)備(Central)。一個(gè)中心設(shè)備可以連接多個(gè)外圍設(shè)備,一個(gè)外圍設(shè)備包含一個(gè)或多個(gè)服務(wù)(services),一個(gè)服務(wù)包含一個(gè)或多個(gè)特征(characteristics)。
使用CoreBluetooth庫(kù),創(chuàng)建CBPeripheralManager,實(shí)現(xiàn)CBPeripheralManagerDelegate代理
創(chuàng)建完該對(duì)象,會(huì)回調(diào)peripheralManagerDidUpdateState:方法判斷藍(lán)牙狀態(tài),藍(lán)牙可用,給外設(shè)配置服務(wù)和特征
注意CBAttributePermissions
當(dāng)中心設(shè)備讀寫(xiě)設(shè)置CBAttributePermissionsReadEncryptionRequired/CBAttributePermissionsWriteEncryptionRequired權(quán)限的Characteristic時(shí),會(huì)彈出彈框,請(qǐng)求建立安全連接
給外設(shè)配置服務(wù)特征后,會(huì)調(diào)用peripheralManager:didAddService:error: 服務(wù)特征全部添加完后發(fā)起廣播,如果在廣播時(shí)設(shè)置CBAdvertisementDataServiceUUIDsKey,會(huì)把該service廣播出去,中心設(shè)備在掃描時(shí)可根據(jù)該uuid找到該設(shè)備。外圍設(shè)備靠不斷發(fā)廣播,使中心設(shè)備發(fā)現(xiàn)它。
當(dāng)中央端連接上了此設(shè)備并訂閱了特征時(shí)會(huì)回調(diào) didSubscribeToCharacteristic:
當(dāng)接收到中央端讀的請(qǐng)求時(shí)會(huì)調(diào)用didReceiveReadRequest:
創(chuàng)建CBCentralManager對(duì)象,實(shí)現(xiàn)CBCentralManagerDelegate代理
回調(diào)centralManagerDidUpdateState:代理方法,當(dāng)central.state==CBManagerStatePoweredOn時(shí),開(kāi)啟掃描,設(shè)置serviceUUIDs可掃描特定外設(shè),CBCentralManagerScanOptionAllowDuplicatesKey設(shè)為NO不重復(fù)掃描已發(fā)現(xiàn)設(shè)備,YES是允許
掃描到設(shè)備會(huì)回調(diào)centralManager:didDiscoverPeripheral:advertisementData:RSSI:,RSS絕對(duì)值越大,表示信號(hào)越差,設(shè)備離的越遠(yuǎn)
關(guān)閉掃描
連接設(shè)備
發(fā)現(xiàn)服務(wù)
發(fā)現(xiàn)特征