iOS藍(lán)牙開發(fā)詳解
隨著物聯(lián)網(wǎng)技術(shù)的高速發(fā)展挤牛,藍(lán)牙開發(fā)也越來越火熱莹痢。不論是智能穿戴設(shè)備還是藍(lán)牙家具,車聯(lián)網(wǎng)藍(lán)牙墓赴,都是通過藍(lán)牙協(xié)議來進(jìn)行通信竞膳。廢話不多說了,先向大家簡單的介紹有關(guān)藍(lán)牙開發(fā)的知識诫硕。藍(lán)牙低能耗(BLE)坦辟,以下介紹的都是圍繞iOS的藍(lán)牙(4.0)框架展開的。
藍(lán)牙開發(fā)分為中心管理者者模式和外設(shè)管理者模式:1.常用的(90%)就是使用中心管理者者模式作為開發(fā)章办,就是我們手機(jī)作為中心管理者锉走,掃描、連接藍(lán)牙外設(shè)藕届;2.外設(shè)管理者模式挪蹭,這個基本用到的比較少,我們手機(jī)自己作為外設(shè)翰舌,自己創(chuàng)建服務(wù)和特征嚣潜,然后通過廣播,把自己的信息廣播出去椅贱,其他的設(shè)備可以掃描連接到我們的手機(jī)懂算。
在做藍(lán)牙開發(fā)之前只冻,最好先了解一些概念:
廣播包: (Advertising)廣播包是一個公開的信息,別人不需要連接你的設(shè)備就能讀取到的信息;(iOS端由于蘋果的限制计技,無法獲取設(shè)備的Mac地址喜德,只能讓硬件放于廣播包中。)
外設(shè): (peripheral)一個藍(lán)牙硬件就是一個外設(shè)垮媒,一個外設(shè)下有一個或者多個服務(wù);
- 服務(wù): (services)藍(lán)牙外設(shè)對外廣播的必定會有一個服務(wù)舍悯,可能也有多個,服務(wù)下面包含著一些特征值睡雇,服務(wù)可以理解成一個端口;
- 特征: (characteristic)特征可以理解成一個HTTP的接口萌衬,一般特征都會有value,也就是特征值它抱。每個特征都有一個或多個權(quán)限秕豫,例如讀、寫观蓄、通知混移。向一個特征寫值可以看做是發(fā)起一個HTTP請求。(和HTTP請求不同的是侮穿,如果你寫入的type和硬件底層的type不配套會寫不進(jìn)去歌径。無論是哪個type,系統(tǒng)都會通過代理回調(diào)告知我們是否寫入亲茅。)因此特征是與外界交互的最小單位;
- UUID:可以理解成藍(lán)牙上的唯一標(biāo)識符回铛,為了區(qū)分不同的服務(wù)和特征,我們就用UUID來代表服務(wù)和特征克锣。
藍(lán)牙連接可以大致分為以下幾個步驟:
- 建立一個Central Manager實例勺届,實現(xiàn)Central代理協(xié)議進(jìn)行藍(lán)牙管理。
- 打開藍(lán)牙娶耍,掃描周圍外設(shè),獲取廣播包數(shù)據(jù)信息饼酿。
- 根據(jù)廣播包數(shù)據(jù)連接指定外設(shè)榕酒。(目前我測試一個APP連接7個外設(shè)是沒有問題,但是一個手機(jī)能連接多少個并沒有測試故俐,如果有知道的請告訴我想鹰。)
- 獲得外設(shè)的服務(wù)。
- 獲得服務(wù)的特征药版。
- 從外設(shè)讀數(shù)據(jù)辑舷。
- 給外設(shè)發(fā)送數(shù)據(jù)。
#pragma mark - 掃描槽片、連接操作
/**
開始搜索
*/
- (void)startScan {
[self.manager scanForPeripheralsWithServices:nil options:nil];
}
/**
開始搜索
*/
- (void)startScanWith:(didDiscoverPeripheral)block {
self.didDiscoverPeripheralBlock = block;
[self.manager scanForPeripheralsWithServices:nil options:nil];//掃描設(shè)備何缓,可設(shè)置掃描的服務(wù)和選項肢础。不填寫可以加快掃描速度。
}
/**
連接指定設(shè)備碌廓,可設(shè)置連接選項
*/
- (void)linkWith:(Device *)device options:(nullable NSDictionary<NSString *,id> *)dic handle:(didConnectPeripheral)block {
//防止多次點擊传轰,連接多次,只讓連接一個谷婆,如果做BLE4.0多連接可以取消這個限定
if (self.linkStatus != BLEManagerUnLinked) {
return;
}
self.linkStatus = BLEManagerLinking;
self.didConnectPeripheralBlock = block;
//iOS端鏈接外設(shè)只能用掃描到的外設(shè)慨蛙,如果連接的外設(shè)為空會crash。
if (device.per) {
[self.manager connectPeripheral:device.per options:dic];
}
[self performSelector:@selector(linkOuttime) withObject:nil afterDelay:5];//超過五秒則認(rèn)為超時纪挎,取消連接
self.linkDevice = device;
}
/**
連接超時
*/
- (void)linkOuttime {
if (self.linkStatus != BLEManagerLinked) {
[self disconnectPeripheral:self.manager peripheral:self.linkDevice.per];
NSError *error = [NSError errorWithDomain:@"連接超時" code:1 userInfo:nil];
self.linkStatus = BLEManagerUnLinked;
self.didConnectPeripheralBlock(NO, error);
}
}
//斷開連接
- (void)disconnectPeripheral:(CBPeripheral *)peripheral {
//斷開連接 如果外設(shè)為空會crash
if (peripheral) {
[self.manager cancelPeripheralConnection:peripheral];
} else {
if (self.didDisconnectPeripheralBlock) {
self.didDisconnectPeripheralBlock(peripheral, nil);
}
}
[self cleanData];
}
- (void)cleanData {
//數(shù)據(jù)回歸初始化狀態(tài)
self.datas = [NSMutableArray array];
self.allDevice = [@{} mutableCopy];
self.linkStatus = BLEManagerUnLinked;
}
#pragma mark - 發(fā)送數(shù)據(jù)
/**
發(fā)送數(shù)據(jù)
@param data 數(shù)據(jù)
@param type 寫入類型
*/
- (void)sendData:(NSData *)data withType:(CBCharacteristicWriteType)type{
//數(shù)據(jù)發(fā)送,發(fā)送的時候,外設(shè)特征值必須存在,發(fā)送的數(shù)據(jù)是NSData類型
/*
具體使用哪種類型期贫,詢問硬件工程師或者查看接口文檔
兩者的區(qū)別在于寫入的通道不同(硬件工程師和我說的),如果type不對應(yīng)則會寫入失敗异袄。
type:
type為CBCharacteristicWriteWithResponse類型時通砍,didWriteValueForCharacteristic會返回寫入調(diào)用的結(jié)果,而CBCharacteristicWriteWithoutResponse則不會隙轻。埠帕。
*/
[self.send sendData:data withType:type];
}
/**
外設(shè)的特征值更新通知
@param peripheral 連接(更新)的外設(shè)
@param characteristic 連接(更新)的外設(shè)的特征值
@param error 錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
//打印出characteristic的UUID和值
NSLog(@"characteristic.uuid = %@, value = %@",characteristic.UUID,characteristic.value);
//!注意,value的類型是NSData玖绿,具體開發(fā)時敛瓷,會根據(jù)外設(shè)協(xié)議制定的方式去解析數(shù)據(jù)
if ([characteristic.UUID.UUIDString isEqualToString:@"readCharacteristic"]) {
NSData *data = characteristic.value;
//把讀取到的數(shù)據(jù)回調(diào)出去
for (getData block in self.getDataBlockArr) {
block(data);
}
} else if ([characteristic.UUID.UUIDString isEqual:readCharacteristicUUID]||[characteristic.UUID.UUIDString isEqual:notiyCharacteristicUUID]||[characteristic.UUID.UUIDString isEqual:writeCharacteristicUUID]) {
NSData *data = characteristic.value;
//把讀取到的數(shù)據(jù)回調(diào)出去
for (getData block in self.getDataBlockArr) {
block(data);
}
}
}
/**
已經(jīng)寫完數(shù)據(jù)回調(diào)
@param peripheral 連接(寫入)的外設(shè)
@param characteristic 連接(寫入)的外設(shè)的特征值
@param error 是否寫入成功,如果error為nil則寫入成功斑匪,否則error會存在
*/
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
NSLog(@"didWriteValue error = %@", error);
}