前言
這一年,公司產(chǎn)品主要產(chǎn)品是智能鎖谓厘,兼帶接入一些其余的智能家居產(chǎn)品,lifeSmart寸谜,物聯(lián)等竟稳。幾乎是跟藍(lán)牙打了一年的交道,不寫點(diǎn)什么實(shí)在說不過去熊痴,也必須寫點(diǎn)什么證明曾經(jīng)來過住练,走你。
什么是庫愁拭?
庫是共享程序代碼的方式讲逛,一般分為靜態(tài)庫和動態(tài)庫。程序運(yùn)行分為三個步驟:編譯岭埠,鏈接盏混,執(zhí)行。編譯作用是將原代碼(程序員手寫代碼)翻譯成目標(biāo)代碼(機(jī)器的二進(jìn)制代碼)惜论,會生成目標(biāo)文件(有多少個實(shí)現(xiàn)文件就會生成多少個目標(biāo)文件)许赃。鏈接就是將各個有關(guān)聯(lián)的目標(biāo)文件和庫文件鏈接,是由機(jī)器完成的馆类,在此過程中經(jīng)常出現(xiàn)linker錯誤混聊。
1,靜態(tài)庫與動態(tài)庫
靜態(tài)庫:鏈接時完整地拷貝至可執(zhí)行文件中乾巧,被多次使用就有多份冗余拷貝句喜。
動態(tài)庫:鏈接時不復(fù)制,程序運(yùn)行時由系統(tǒng)動態(tài)加載到內(nèi)存沟于,供程序調(diào)用咳胃,系統(tǒng)只加載一次,多個程序共用旷太,節(jié)省內(nèi)存展懈。
2,iOS靜態(tài)庫形式和動態(tài)庫形式:
靜態(tài)庫:.a和.framework
動態(tài)庫:.dylib和.framework
3供璧,ramework靜態(tài)庫和動態(tài)庫的區(qū)分:
系統(tǒng)的.framework是動態(tài)庫存崖,我們自己建立的.framework是靜態(tài)庫
4,.a和.framwork的區(qū)別:
.a是一個純二進(jìn)制文件睡毒,.framework中除了有二進(jìn)制文件外還有資源文件来惧。
.a文件不能直接使用,至少要有.h文件配合吕嘀,.framework文件可以直接使用违寞。
.a + .h + sourceFile = .framework
藍(lán)牙開發(fā)基礎(chǔ)操作
1,建立中心管理者
//引用一下庫的頭文件 #import <CoreBluetooth/CoreBluetooth.h>
//遵循<CBCentralManagerDelegate,CBPeripheralDelegate>兩個代理
CBCentralManager *manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
2偶房,掃描外設(shè)(discover)
調(diào)用1方法后趁曼,系統(tǒng)會自動檢測手機(jī)藍(lán)牙狀態(tài),并進(jìn)入此方法棕洋。當(dāng)發(fā)現(xiàn)藍(lán)牙狀態(tài)是開啟狀態(tài)挡闰,你就可以進(jìn)行下一步掃描外設(shè)操作了,如果為關(guān)閉狀態(tài)掰盘,系統(tǒng)會自動彈出讓用戶去設(shè)置藍(lán)牙摄悯,這個不需要我們開發(fā)者關(guān)心。
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
if (central.state==CBCentralManagerStatePoweredOn) {
//NSLog(@"藍(lán)牙已就緒,開始掃描外設(shè)");
//開始掃描
[self.manager scanForPeripheralsWithServices:nil options:nil];
}else{
//NSLog(@"請檢查藍(lán)牙狀態(tài)");
}
}
3愧捕,掃描到外設(shè)奢驯,判斷是否為目標(biāo)設(shè)備,之后進(jìn)行連接操作
//掃描到設(shè)備會進(jìn)入方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
if ([peripheral.name isEqualToString:@""]) {
self.thePerpher = peripheral;
//停止掃描
[central stopScan];
//連接設(shè)備
[central connectPeripheral:peripheral options:nil];
}
}
這里有一個值得注意的地方次绘,安卓是可以直接掃到藍(lán)牙設(shè)備的MAC地址然后進(jìn)行連接的瘪阁,但是自從iOS7以后就無法從API直接獲取設(shè)備的MAC地址,所以絕大多數(shù)做法通常是判斷設(shè)備名字peripheral.name邮偎,像我們公司的鎖管跺,藍(lán)牙名稱一般都是dyBKMWWtwR這種,所以做法是截取頭兩位禾进,判斷是否含有“dy”字段豁跑。
4,連接設(shè)備成功泻云,開始掃描服務(wù)
//連接到外設(shè)成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
//這里遵循的代理是CBPeripheralDelegate艇拍,后續(xù)的掃描服務(wù)回掉,掃描特征回掉宠纯,都是此代理里面的回掉方法
[peripheral setDelegate:self];
//掃描服務(wù)
[peripheral discoverServices:nil];
}
//連接外設(shè)失敗
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
NSLog(@"連接到外設(shè) 失斒缜恪!%@ %@",[peripheral name],[error localizedDescription]);
}
4征椒,掃描到服務(wù)(Services)娇哆,開始掃描特征(Characteristics)
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if (error) {
NSLog(@"掃描外設(shè)服務(wù)出錯:%@-> %@", peripheral.name, [error localizedDescription]);
return;
}
//開始掃描特征
for (CBService *service in peripheral.services) {
[peripheral discoverCharacteristics:nil forService:service];
}
}
5, 掃描到特征
//掃描到特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error) {
NSLog(@"掃描特征失敳取碍讨!");
return;
}
//獲取Characteristic的值
for (CBCharacteristic *characteristic in service.characteristics){
if ([characteristic.UUID.UUIDString isEqualToString:@"DE4001"]) {
//外設(shè)訂閱特征的通知,否則無法收到外設(shè)返回給手機(jī)的數(shù)據(jù)
[self.thePerpher setNotifyValue:YES forCharacteristic:characteristic];
}else if ([characteristic.UUID.UUIDString isEqualToString:@"DE4002"]) {
//CBCharacteristic 全局對象
self.theSakeCC = characteristic;
}else if ([characteristic.UUID.UUIDStringisEqualToString:@"DE4003"]) {
//CBCharacteristic 全局對象
self.encryptSakeCC = characteristic;
}
}
}
這里是一個很重要的地方蒙秒,特征值一般是硬件定義好的勃黍,比如說我這里,DE4001是回調(diào)數(shù)據(jù)read特征值(只能用來讀數(shù)據(jù))晕讲。DE4002是加密透傳傳輸Write特征值覆获,DE4003是不加密透傳傳輸Write特征值马澈,只能用來寫數(shù)據(jù)。
6弄息,給硬件發(fā)送數(shù)據(jù)
[self.thePerpher writeValue:sendData forCharacteristic:self.theSakeCC type:CBCharacteristicWriteWithoutResponse];
這里要注意在實(shí)際開發(fā)過程中痊班,根據(jù)公司的通信協(xié)議,選擇5中保存的加密或者不加密特征值發(fā)送數(shù)據(jù)摹量。
7涤伐,掃描到具體的值(硬件給App回掉數(shù)據(jù)的地方)
//掃描到具體的值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
if (error) {
NSLog(@"掃描特征值失敗:%@-> %@",peripheral.name, [error localizedDescription]);
return;
}
//正常情況下characteristic.value需要進(jìn)行解密操作操作之后才能拿來判斷,實(shí)際開發(fā)過程中根據(jù)協(xié)議來處理
if (characteristic.value) {
NSString *withoutSpaceStr = [[self dataToHexStringWithData:characteristic.value] substringToIndex:4];
if ([withoutSpaceStr isEqualToString:@"0100"] ) {
//請輸入管理員密碼
}
if ([withoutSpaceStr isEqualToString:@"1101"] ) {
//管理員密碼錯誤
}
if ([withoutSpaceStr isEqualToString:@"0100"] ) {
//開鎖失敗缨称,時間誤差不在允許的范圍內(nèi)
}
}
}
9凝果,斷開連接(disconnect)
- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
斷開與硬件的連接調(diào)用此方法,但是調(diào)用此方法后并不會立馬斷開睦尽,只是APP層面斷開了連接器净,物理層并沒有完全斷開,大約5s后才能完全斷開当凡,所以如果業(yè)務(wù)需求上有需要立馬斷開的掌动,建議除了調(diào)用此方法前,同時發(fā)送一個跟硬件約定的斷開命令宁玫,如果是封裝的藍(lán)牙單例類粗恢,再釋放此單例。
藍(lán)牙通訊常見加密方式
藍(lán)牙開發(fā)過程中遇到的問題匯總
1欧瘪,掃描設(shè)備時根據(jù)peripheral.name判斷目標(biāo)設(shè)備眷射,同一設(shè)備不同狀態(tài)下藍(lán)牙名稱跟更新不及時,會出現(xiàn)緩存問題
比如我們的鎖佛掖,普通狀態(tài)是dyBKMWWtwR妖碉,處于等待添加配對狀態(tài)時dyBKMWWtwA,解決辦法取廣播中的藍(lán)牙名稱芥被,實(shí)時更新不會存在緩存問題
[advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
2欧宜,無論怎么調(diào)試,確認(rèn)發(fā)送數(shù)據(jù)加密拴魄,格式等都沒問題冗茸,但writeValue之后都收不到消息
我遇到這個問題的原因是跟writeValue的type有關(guān),type有兩個值匹中,CBCharacteristicWriteWithoutResponse和CBCharacteristicWriteWithResponse夏漱,這兩個值,都可以收到回調(diào)消息顶捷,之前遇到一個坑挂绰,用的是WithResponse這個type,不管怎么調(diào)試服赎,都收不到硬件返回的消息葵蒂,安卓卻可以交播,一度開始懷疑人生,百思不得其解之后践付,嘗試著換成了WithoutResponse秦士,秒收!在此大膽的猜想應(yīng)該是硬件那邊的某些設(shè)置問題荔仁。
3,拼裝的NSData數(shù)據(jù)MD5之后芽死,經(jīng)常出現(xiàn)同一數(shù)據(jù)結(jié)果不一樣
常用的md5算法如下所示乏梁,但是當(dāng)我加密一長串字節(jié)數(shù)組,比如<0213 2123 2123 1020 2123 0002>時关贵,結(jié)果會發(fā)生變化遇骑,根本原因是[data bytes]返回的結(jié)果不一樣。 解決辦法揖曾,CC_MD5方法第二個參數(shù)不用original_str的結(jié)果落萎,直接用傳入的字節(jié)數(shù)組data長度。將CC_MD5方法替換成這個CC_MD5(input, (uint)data.length, result)即可炭剪,至于為什么[data bytes]返回的結(jié)果會不一樣练链,一陣google之后尚未找到合理的解釋,有知道的同學(xué)可以科普一下奴拦。
+ (NSString*)getMD5WithData:(NSData *)data {
const char* original_str = (const char *)[data bytes];
unsigned char digist[CC_MD5_DIGEST_LENGTH];
CC_MD5(original_str, (uint)strlen(original_str), digist);
NSMutableString* outPutStr = [NSMutableString stringWithCapacity:0];
for(int i =0; i<CC_MD5_DIGEST_LENGTH;i++){
[outPutStr appendFormat:@"%02x",digist[i]];
}
return [outPutStr lowercaseString];
}